Rails in what order do model callbacks happen for

2019-07-27 03:58发布

问题:

I know that the general order in which models are saved is the deepest child first, and then gradually up to the parent. But I'm wondering with respect to other callbacks, does it happen something along the lines of:

ChildA - before validation
ChildB - before validation
Parent - before validation

ChildA - after validation
ChildB - after validation
Parent - after validation

ChildA - before save
ChildB - before save
Parent - before save

...

OR along the lines of:

ChildA - before validation
ChildA - after validation
ChildA - before save
...

ChildB - before validation
ChildB - after validation
ChildB - before save
...

Parent - before validation
Parent - after validation
Parent - before save
...

The reason this is important is that I have callbacks that adjust attributes, and the adjustability of an attribute on a model depends on the attributes of other models.

One example is that I want the Parent to auto-set its status attribute to be Complete if ChildA and child B'sstatusattributes are bothCompleteand the children are bothvalid`.

I tried to test this using puts statements, but apparently, that leads to some weird behaviour (see this question: Nested form validation statements repeating multiple times), and I'm afraid it's not representative.

I definitely read the Rails Guides, but maybe I'm blind because I didn't see a reference to this anywhere...

回答1:

While putting this kind of state machine logic in callbacks might be tempting, it makes for a hard to understand code. Consider instead having a dedicated method gathering and mutating your data in a more straightforward way.

This would also help with data consistency and tests, as described in this blog post.



回答2:

Interesting question. By default callback defined in the parent is run first. prepend: true option allows you to tune that behaviour.

See "Ordering callbacks" section in Rails API. It's also worth checking "Inheritable callback queues" section, since redefining a method is not the same as using macros.

P.S. All stated above is valid at least for version 4.2.5.2



回答3:

By experimenting with rails I have encountered that the parent is validated first and then child but no rails core member have pronounced about this.

That's why you can move logic to parent:

class Sale
  has_many :details
  before_validation :evaluate_details
  # validations

  def evaluate_details
    self.details.each do |detail|
      detail.generate_info
      detail.items.each do |item|
        item.dance
      end
    end
  end
end

class Detail
  belongs_to :sale
  has_many :items
  # validations

  def generate_info
    # Do things
  end
end

class Item
  belongs_to :detail
  # validations

  def dance
    # Dancing
  end
end

This prevents multiple executions and manages the execution order.

PD: Also you can avoid the before_validation callback and call the method manually.