belongs_to touch: true not fired on association.build and nested attribute assignment
Asked Answered
L

1

7

In the OpenProject application we have two models:

CustomField

class CustomField < ActiveRecord::Base
  has_many :custom_options, -> { order(position: :asc) }, dependent: :delete_all
  accepts_nested_attributes_for :custom_options
  ...
end

CustomOption

class CustomOption < ActiveRecord::Base
  belongs_to :custom_field, touch: true
  ...
end

Then in the custom field controller we modify the options of a custom field via mass assignment and save the record:

@custom_field.attributes = get_custom_field_params

if @custom_field.save 
  ...

Because of touch: true being configured on the custom_field association in CustomOption I would have expected the custom field's updated_at attribute to be updated at this point which does not happen however. The log does not show any SQL requests similar to UPDATE custom_fields SET update_at = ..... The changes to the custom options themselves, regardless of whether it is adding a custom option or modifying one, are correctly persisted.

Debugging showed:

  • The same erroneous behaviour when using custom_field.custom_options.build and a subsequent custom_field.save
  • The desired behaviour when using custom_field.custom_options.create
  • The desired behaviour when using CustomOption.create custom_field: x
  • The desired behaviour when using custom_field.custom_option[x].destroy

I know I could work around the problem by defining after_save callbacks, but this behaviour really irks me as it was unexpected.

Is the behaviour as outlined above desired and documented? If so, where can I find this information?

Leafy answered 27/9, 2017 at 12:39 Comment(0)
M
10

The problem is that if the parent model has any after_commit callback (maybe some others), :touch stops working when autosaving associated records, even if the callback is not doing anything.

Your CustomField model has after_commit callback coming from acts_as_list gem. This breaks :touch.

There was a similar issue https://github.com/rails/rails/issues/26726 . That issue was associated with accepts_nested_attributes_for, but I am sure that accepts_nested_attributes_for has nothing to do with it, at least in your case.

Added by the author of the question (@ulferts):

Defining inverse_of: 'custom_field' on the custom_options association in CustomField seems to circumvent the problem for unknown reasons.

has_many :custom_options,
         -> { order(position: :asc) }, 
         dependent: :delete_all,
         inverse_of: 'custom_field'
Marney answered 27/9, 2017 at 16:17 Comment(3)
Thanks a lot @chumakoff, your analysis is spot on. Also thanks for the link. after_save callbacks it is, then. I actually had the same suspicion but only deactivated the acts_as_list on the child (CustomOption).Leafy
Found out that it is possible to circumvent the problem by defining inverse_of on the has_many association. I send an edit request to the answer.Leafy
Thanks for this! I approved the request )Marney

© 2022 - 2024 — McMap. All rights reserved.