Active Record Callbacks shouldn't modify other records
In Ruby on Rails, After Record Callbacks allow you to hook into the lifecycle of Active Record objects. Commonly used callbacks are after_create
or before_validation
. In this post, I want to highlight a bad practice that is sometimes used in callbacks.
Let’s say we have a User
model that has an associated Account
. The idea is that every user also has an account, which we use to specifiy more details like billing information.
Now, since every time we create a new User
, we also want to create a new Account
, we might be tempted to do something like this in our User
model:
class User < ApplicationRecord
has_one :account
after_create :create_account
def create_account
Account.create(user: self)
end
end
This is almost never a good idea. Creating or updating one model should never touch another model.
The main reason is that it tangles up concerns (remember: we like separation of concerns) and is not very explicit. A colleague working with you on this codebase might not expect that this side effect will happen. After all, in other places in your code where you create your user, you only do User.create
and it magically also creates an Account
for this user.
Another reason is that, while most of the time you want to create an associated Account
, maybe there will be times where you don’t.
A further reason is testing. If you use factories to set up your test data, creating a User
factory instance will not automatically create an Account
factory instance, but it will create an Account
record in the test database. Things will get messy from there.
Instead of using a callback, I recommend just creating the Account separately or wrapping these two commands into a method that lives in the User
model, like this:
class User < ApplicationRecord
has_one :account
def self.create_with_account(attributes)
user = User.create(attributes)
Account.create(user: user)
end
end