3 reasons to avoid Rails callbacks

3 reasons to avoid Rails callbacks

I remember when I was starting and saw the Active Record callbacks for the first time: “Wow they are so useful!” - I thought at the time. Oh god, how I was wrong.

I mean, they seem useful, and indeed they are useful for the first weeks. But then your application starts to grow and what before was pure joy becomes a series of headaches that culminate in your precious time being wasted in a lot of different ways.

In this post we will see why callbacks can hurt your code and one good way to avoid them.

1. They hide your code behavior

1
2
3
class Egg < ActiveRecord::Base
  before_save :hatch
end

So simple, so dangerous.

It may seem obvious for us right now that a Egg will hatch before being saved to the database, but a developer in the future (you included) could be working on other parts of the code and forget that whenever an Egg gets saved it will hatch.

Imagine that the feature this developer is working needs the eggs unhatched. Yes, he may be in for a very unpleasant surprise. And all because this behavior was hidden from him.

2. It’s hard to get around them later

Ok, let’s keep on the Egg scenario: at the time of the implementation, it made perfect sense that any Egg should be hatched before being saved on the database.

But in software, as in life, things change. A lot. All the time. And the time came when a feature needed to save an unhatched Egg. Now the rails callbacks are standing in your way.

Well, of course we could do something like that:

1
2
3
4
5
6
7
8
9
class Egg < ActiveRecord::Base
  before_save :hatch

  def hatch
    if self.hatchable?
      self.status = :hatched
    end
  end
end

But keep stacking up those ifs and you will see how chaotic a method can become.

3. They make your tests harder to write and mantain

Then things end up like this:

1
2
3
4
5
6
7
8
9
10
class Egg < ActiveRecord::Base
  before_save :hatch

  def hatch
    if self.hatchable?
      self.status = :hatched
      self.bird   = Bird.create(species: self.species)
    end
  end
end

So now you have eggs creating birds out of callbacks. Go grab some aspirin. This is sort of an extreme example, because this is SUCH a bad idea. But let’s keep going with it.

So you decide to test this thing, and you have some extra work handling edge cases, like testing the scenario of an Egg that was saved in the database before the bird feature was available.

Then everything works and you think: life is beautiful, right? Wrong.

You notice your tests slowing down. That is what you get when create birds every time you save an egg (that means one more round of ActiveRecord magic and one more operation in the database, both of which take their price in the tests performance).

Meet an alternative to Rails callbacks

Of course I would be a jerk if I ended this post telling you to avoid callbacks without giving you at least one good alternative, right?

Service Objects

They are one of my favorite “unofficial” abstractions in wide use on the Ruby on Rails community. In the future I wish to talk more about them, but for now I give you this excellent article from the Engine Yard blog that explains it quite nicely.

There you have it, the next time you think about adding an ActiveRecord callback, be sure to know what you are doing.

Victor A.M.

Victor A.M.
Yet another passionate web developer, mostly experienced with Ruby on Rails and Vue.js.
Currently living in São Paulo and working at Plataformatec.

Vue.js Advanced: Mastering Events

In this post we will take a deep dive into the Vue.js event system, talking about the good practices, helpful tricks and common pitfalls that you will can face. Continue reading

Ruby object interfaces: Public x Private

Published on April 24, 2016

Rails transactions: The Complete Guide

Published on April 17, 2016