Close
Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

LABS
ActiveRecord callbacks, autosave, before this and that, etc.

The Ugly Truth

On a recent project, we had an ActiveRecord model that declared some relationships and callbacks like so:

belongs_to :credit_card
before_create :build_credit_card

The intent was that build_credit_card would build the associated CreditCard instance, and ActiveRecord’s default :autosave feature on the belongs_to would save it.

What we discovered was that no CreditCard object was being persisted. We confirmed that :autosave is on by default for belongs_to relationships, so we couldn’t immediately understand why the new CreditCard wasn’t being created.

Googling proved futile, so we dove right in to the ActiveRecord source- and boy did we have a good laugh about 10 minutes later.

What we found was that the :autosave option works by simply declaring a before_save callback- that makes perfect sense.

In our case, however, we were building the object to be autosaved in a before_create callback, which ActiveRecords runs after the before_save callbacks (cf. the callback ordering docs).

So our first problem was that we needed to move the call to build_credit_card from a before_create callback to a before_save :on => :create callback.

Did you catch that? There is a difference between before_create and before_save :on => :create. A big difference.

While I understand the how and why of this, the semantics don’t make it obvious. So beware!

Now with our declarations changed to

belongs_to :credit_card
before_save :build_credit_card, :on => :create

We ran our tests again, and, still, no love. Ahhh, we’ve still got an ordering problem. In addition to the ordering semantics detailed in the docs, ActiveRecord also runs callbacks within a single group in the order in which they are declared. So, even though we changed the call to build_credit_card to occur in a before_save, it was still occurring after the :autosave before_save callback, because of the declaration order.

Finally, we changed our declarations to

before_save :build_credit_card, :on => :create
belongs_to :credit_card

and our tests were happy.

Takeaways

  • When using autosave with any ActiveRecord association, be very careful of callback ordering if you are building or modifying the inverse objects using ActiveRecord callbacks.

  • before_create isn’t ever the same thing as before_save :on => :create, even if it sounds like it should be.

Comments
  1. Good takeaways, but unfortunately, that’s what using the callbacks will give you. I have code which I wrote but haven’t yet used in production which can help with that: https://github.com/francois/komando

    What Komando does is give you commands, rather than callbacks, enabling you to order your operations in a sane manner:

    class CreateCustomer
    include Komando::Command

    mandatory_steps do
    # Contrast with ActiveRecord’s callbacks and note that
    # the ordering of operations is crystal clear: no mistake is possible
    card = build_credit_card
    customer.card = card
    customer.save!
    end
    end

    It’s an idea I’ve had in the back of my head for a while. I’ll likely use that at some point, just not yet.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *