Search code examples
ruby-on-railscallbacknestednested-formsnested-attributes

Rails before_update callback with nested attributes


I have two models (lets call then A and B).

A has_many bs and B belongs_to A.

class A < ApplicationRecord
  has_many :bs, dependent: :destroy, inverse_of: :a
  accepts_nested_attributes_for :bs, reject_if: :all_blank, allow_destroy: true
  validates_associated :bs
end


class B < ApplicationRecord
  belongs_to :a, inverse_of: :bs
  before_update :do_something, unless: Proc.new { |b| b.a.some_enum_value? if a }

  def do_something
    self.some_field = nil
  end

end

Other than that, B has a before_update callback that sets some_field to nil if A has some_enum_value set.

Since this relation is used on a nested form, that before_update from B is only being called if I update a attribute form B. If I only change a value form A that callback is not called.

How can I call B's before_update when A is updated?

Thanks in advance.


Solution

  • For belongs to associations you can use the touch option:

    class B < ApplicationRecord
      belongs_to :a, inverse_of: :bs, touch: true
    end
    

    Which would update a.updated_at when you update B. However this option does not exist for has_many relations since it could have potentially disastrous performance consequences (If an A has 1000s or more Bs).

    You can roll your own however:

    class A < ApplicationRecord
      has_many :bs, dependent: :destroy, inverse_of: :a
      accepts_nested_attributes_for :bs, reject_if: :all_blank, allow_destroy: true
      validates_associated :bs
      after_update :cascade_update!
    
      def cascade_update!
        # http://api.rubyonrails.org/classes/ActiveRecord/Batches.html#method-i-find_each
        bs.find_each(batch_size: 100) do |b|
          b.update!(updated_at: Time.zone.now)
        end
      end
    end