after_create bug in Rails 3
I ran into this callback ordering issue while working on a Rails project recently and decided to investigate. After writing some test rails apps I am pretty confident this is a bug in Rails 3.0.x. If you believe otherwise, please leave a comment or explanation.
Taking a step back from the actual code, in an ORM (Object Relational Model) I would expect that callbacks from objects are consistent in their ordering. That is, if I have a callback that fires when an object has been saved to the database, that callback will fire at the same time (after the object has been saved to the database) regardless of any dependent objects or where the callback has been declared. Here’s some code to demonstrate this:
Simple, right? Almost. The problem in Rails is that the after_create callback doesn’t exactly work that way. When it fires is actually dependent on the order in which it was defined. What’s more - this isn’t actually the case with the after destroy callback. Here’s some code to demonstrate the issue:
Take a second and look at that - do you see what’s wrong? The
User#after_create
callbacks are called at opposite ends of the Post#after
create callbacks, but the User#after_destroy callbacks are called at the end.
What does this mean? Well basically that after_create is not actually called
after the record is created (and saved to the database) - it’s called “after the
record is created and any dependent relations defined before it are also
created”. Now that just seems wrong to me. Especially when the after_destroy
callback behaves in a different way.
It seems that the flow should work like this:
- The user is saved.
- The
User#after_create
callbacks are executed. - The post is saved
- The
Post#after_create
callbacks are executed.
Especially when the opposite is true for after_destroy
:
- The post is destroyed.
- The
Post#after_destroy
callbacks are executed. - The user is destroyed.
- The
User#after_destroy
callbacks are executed.
The problem right now is that the User#after_create
callbacks are being called
in two places - once after the user is created (if they are defined before the
has_many :posts
declaration) and once after the Post#after_create
callbacks
are executed (if they are defined after the has_many :posts declaration).
If you’re into TDD, you would check that the first case actually produces this result:
=> User - first after create
=> User - second after create
=> Post - first after create
=> Post - second after create
So why is this a problem? Why not just define all your callbacks before your relations? The first problem with this is that it is inconsistent - after destroy and after_create should behave in the same manner. The second problem is that your code might fail and it’s not obvious why.
While this example is trivial it illustrates how easy it is to mix up the order or definitions in Ruby. Unbeknownst to us, simply looking at the User class, this would actually cause our :do_something callback to be executed in the wrong order.