fix(accounts): persist subtype when creating an account (#2356)

The Account#subtype= writer delegates to accountable&.subtype=, which is a
silent no-op while the accountable is nil. On create the accountable is built
from accountable_attributes via accepts_nested_attributes_for, but the form
submits subtype as a top-level account attribute. Mass-assignment applies
subtype before accountable_attributes, so the selected subtype was dropped on
create (update worked because the accountable already exists).

Build the accountable from the delegated type inside the writer when it is not
yet present, so the value is preserved; the later accountable_attributes
assignment (update_only) updates the same record.

Add regression tests covering the create flow and the assignment ordering.
This commit is contained in:
Artem Danilov
2026-06-16 10:57:35 +02:00
committed by GitHub
parent b9716a0485
commit e342ac4ad1
2 changed files with 41 additions and 2 deletions
+11 -2
View File
@@ -95,9 +95,18 @@ class Account < ApplicationRecord
delegated_type :accountable, types: Accountable::TYPES, dependent: :destroy
delegate :subtype, to: :accountable, allow_nil: true
# Writer for subtype that delegates to the accountable
# This allows forms to set subtype directly on the account
# Writer for subtype that delegates to the accountable.
# This allows forms to set subtype directly on the account.
#
# On create the accountable may not be built yet: mass-assignment can apply
# `subtype` before `accountable_attributes` (which is what builds the
# accountable via accepts_nested_attributes_for). With no accountable in place
# `accountable&.subtype = value` is a silent no-op and the chosen subtype is
# dropped. Build the accountable from the delegated type first so the value is
# preserved; the later `accountable_attributes` assignment (update_only) then
# updates this same record instead of building a new one.
def subtype=(value)
self.accountable = accountable_class.new if accountable.nil? && accountable_type.present?
accountable&.subtype = value
end