22

I'm running into a bizarre issue where children callbacks aren't fired when the parent is updated...

I have the following model setup:

class Budget < ActiveRecord::Base
  has_many :line_items
  accepts_nested_attributes_for :line_items
end

 

class LineItem < ActiveRecord::Base
  belongs_to :budget

  before_save :update_totals

  private
  def update_totals
    self.some_field = value
  end
end

In my form, I have the fields nested (built using fields_for):

= form_for @budget do |f|
  = f.text_field :name
  = f.fields_for :line_items do |ff|
    = ff.text_field :amount

Why is the update_totals callback on the child never fired / what can I do to make it fire?

sethvargo
  • 26,739
  • 10
  • 86
  • 156
  • Did you ever find the solution to this? – Steve Feb 27 '12 at 02:48
  • no ... :(. I ended up just writing a bunch of weird callbacks – sethvargo Feb 28 '12 at 05:03
  • I built a small sample application and was unable to reproduce your issue. Are you certain that the callback is not being fired? The easiest way to make sure is to raise an exception inside the method. If the exception is being raised, then we know that the callback is being fired therefore maybe there's a flaw in the code of the callback, not the callback itself. – Carlos Ramirez III Feb 20 '12 at 20:50
  • I'm positive. I didn't use exceptions, I use `puts` statements, but nothing is showing up in the development log – sethvargo Feb 20 '12 at 21:12
  • Hm even with ```puts``` I am still able to see the output in my own development log. There must be something else at work but I'd need to know a bit more about your application to see what the issue is. – Carlos Ramirez III Feb 20 '12 at 21:47

2 Answers2

39

I had the same issue. before_save callback is not called when the model is not changed.

You're updating line_items, not the budget, so rails thinks that it is not updated and doesn't call save for it.

You need to change before_save to after_validation so it will be called even if model has no changed attributes. And when in this callback you change some attributes, rails will see that your model had changed and will call save.

Anton Dieterle
  • 656
  • 8
  • 8
  • Thanks, @Nico, removed that from answer. For some reason I thought that it should trigger callbacks – Anton Dieterle Jun 14 '13 at 20:08
  • 1
    I do have the same problem with after_save , what should I do @Nico – ratnakar Mar 28 '15 at 12:05
  • Doesn't after_validation fire every time, even if validation fails? Whereas before_save only fires if validation passes? – weexpectedTHIS Apr 21 '16 at 03:05
  • @weexpectedTHIS, I'm not sure about it, maybe you're right. But you can add `if: :valid?` to the `after_validation` callback in that case – Anton Dieterle May 24 '16 at 19:30
  • 1
    in my case on the after_validation method the first line I added is: return if errors.any? – Cris R Jan 15 '17 at 17:16
  • @AntonDieterle ```if: :valid?``` after a ```after_validation``` callback would result in a ```stack level too deep``` error as it starts a new validation. Therfor, @Cris R's solution is the correct one. – TomDogg Aug 03 '17 at 12:54
10

Old question, I know, but it still comes up first in search. I think this article has a solution:

Rails, nested attributes and before_save callbacks

If I'm understanding that article correctly, the problem (as @AntonDieterle explains in his answer) is that the child callback isn't triggered because the parent isn't "dirty." This arcticle's solution is to "force" it to be dirty by calling attr_name_will_change! on a parent attribute that, in fact, does not change. See [Active Model Dirty] in the Rails API2.

Anton's solution of using after_validation instead of before_save sounds simpler, but I wanted to put this out there an an alternative.

Mark Berry
  • 17,843
  • 4
  • 58
  • 88