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 asbefore_save :on => :create
, even if it sounds like it should be.
About the Author
![]()