ActiveRecord callbacks? Nah, extend associations!
In the URUG (Utah Ruby User Group), there was discussion about the proper use of ActiveRecord callbacks on the mailing list. Mike Moore, the organizer of MountainWest RubyConf and the Ruby Web Conference, shared some great insights with the rest of us. See the original discussion for more detail.
Mike Moore shared the approach of extending ActiveRecord associations as an alternative that, in many cases, may provide a more appropriate solution.
What does that look like? This is an example for an alternative to an after_create callback.
class Project < ActiveRecord::Base has_many :iterations do def build_next # Build a new iteration and set the start date. end def create_next next = build_next next.save next end end end
And then you can call it as easily as this.
project = project.find 1234 next = project.iterations.create_next
Now the Iteration model isn't responsible for knowing how to set the desired start date. And you aren't adding that code to the Project either. Its cleanly isolated into the association.
When the code to add is more complicated, extract it to a module and "extend" the association.
module NextIteration def build_next # Mind bending logic here end def create_next # ditto end end class Project < ActiveRecord::Base has_many :iterations, :extend => NextIteration end
This keeps the model's association area nice and clean.
Now, why would we want to avoid callbacks? Mike elaborates with this:
AR callbacks are problematic because it affects every instance of your model, not to mention being a bit magical and indirect. I'm of the opinion that the approach doesn't scale well for business logic like you are using them for. Suppose your application changes and you don't want every Iteration to run that code when created. Or are migrating data and want to use those models but don't want the callbacks. Or, you add someone new to the project and they are unaware of the behavior.
But don't take my word for it. I suggest googling for "activerecord callbacks" and see how many blog entries are positive vs. negative.
I try to favor explicit over magical. But I have a deep resentment for overly wordy code or shoving logic where it doesn't belong. Luckily Rails is designed well enough that it gives you many ways to solve this issue.
Mike later elaborates on when he feels AR callbacks are appropriate:
The question remains: then what are AR callbacks good for? They are good for orthogonal side effects. Logic that is outside of the main flow of your application. But the key phrase is "side effects". Things like logging. Or event notifications. Secondary systems that don't block. If your logging or eventing systems suddenly crashed your application should still continue to work, right?
Learning all this Ruby goodness makes me happy. :) I was also surprised to find in the APIDock page, that it has been available in Rails for a long time! That's nice since I actively work in projects on different versions of Rails.
Refer to the Rails docs here or here for more info.