Reasons I do not like ActiveRecord callbacks:
- They are confusing
before/after_commitis called when creating, updating, destroying an object;
before/after_saveis called when creating and updating an object;
When creating an object,
before_saveis called before
after_saveis called before
after_create… And the list goes on. This really confuses me sometimes, if I do not pay too much attention, I might use some of them wrong.
- They are implicit
It happened to me a couple times, when I tried to create or update a record, some callbacks are triggered unexpectedly. This really irritates me.
In most of the cases when you try to “do something after an active record object is created or updated”, using callbacks could be considered as the code smell.
To prevent it, I like to use
tapto “do something” explicitly.
Here is an example:
Say we have a user model:
class User < ActiveRecord::Base after_create :do_something def do_something puts 'do something...' end end
when I create a new user,
do_somethingcallback will be triggered. While in most situation it work, however creating user object is tightly coupled with
do_somethingmethod. It causes troubles when you don't mean to call
do_somethingbut you forgot this callback was defined in your model.
To refactor it, let’s remove
after_createcallback, and use
# app/models/user.rb class User < ActiveRecord::Base def do_something puts 'do something...' end end # app/controllers/users_controller.rb class UsersController < ActionController::Base def register user = User.create(user_params).tap do |user| user.do_something end end end
This basically does the same as
after_create. You may ask what about
createmethod can take a block, when you need to perform a
before_createaction, simply pass a block to do it.
class UsersController < ActionController::Base def register user = User.create(user_params) do |user| puts 'this is called before creating' user.some_attribute = 'value' user.do_something end end end
When a block is passed to
createmethod, ActiveRecord yield a value
User.new(user_params), which is user, a
Userinstance in this case, we can set attribute to it, call model methods and other operations based on your domain logic, after that,
user.savewill be called. By this means, we will be able to do a