February 13, 2016
This blog is part of our Rails 5 series.
Before Rails 5, returning false
from any before_
callback in ActiveModel
or ActiveModel::Validations
, ActiveRecord
and ActiveSupport
resulted in
halting of callback chain.
class Order < ActiveRecord::Base
before_save :set_eligibility_for_rebate
before_save :ensure_credit_card_is_on_file
def set_eligibility_for_rebate
self.eligibility_for_rebate ||= false
end
def ensure_credit_card_is_on_file
puts "check if credit card is on file"
end
end
Order.create!
=> ActiveRecord::RecordNotSaved: ActiveRecord::RecordNotSaved
In this case the code is attempting to set the value of eligibility_for_rebate
to false. However the side effect of the way Rails callbacks work is that the
callback chain will be halted simply because one of the callbacks returned
false
.
Right now, to fix this we need to return true
from before_
callbacks, so
that callbacks are not halted.
Rails 5 fixed this issue by adding
throw(:abort)
to explicitly halt callbacks.
Now, if any before_
callback returns false
then callback chain is not
halted.
class Order < ActiveRecord::Base
before_save :set_eligibility_for_rebate
before_save :ensure_credit_card_is_on_file
def set_eligibility_for_rebate
self.eligibility_for_rebate ||= false
end
def ensure_credit_card_is_on_file
puts "check if credit card is on file"
end
end
Order.create!
=> check if credit card is on file
=> <Order id: 4, eligibility_for_rebate: false>
To explicitly halt the callback chain, we need to use throw(:abort)
.
class Order < ActiveRecord::Base
before_save :set_eligibility_for_rebate
before_save :ensure_credit_card_is_on_file
def set_eligibility_for_rebate
self.eligibility_for_rebate ||= false
throw(:abort)
end
def ensure_credit_card_is_on_file
puts "check if credit card is on file"
end
end
Order.create!
=> ActiveRecord::RecordNotSaved: Failed to save the record
The new Rails 5 application comes up with initializer named
callback_terminator.rb
.
ActiveSupport.halt_callback_chains_on_return_false = false
By default the value is to set to false
.
We can turn off this default behavior by changing this configuration to true
.
However then Rails shows deprecation warning when false
is returned from
callback.
ActiveSupport.halt_callback_chains_on_return_false = true
class Order < ApplicationRecord
before_save :set_eligibility_for_rebate
before_save :ensure_credit_card_is_on_file
def set_eligibility_for_rebate
self.eligibility_for_rebate ||= false
end
def ensure_credit_card_is_on_file
puts "check if credit card is on file"
end
end
=> DEPRECATION WARNING: Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails. To explicitly halt the callback chain, please use `throw :abort` instead.
ActiveRecord::RecordNotSaved: Failed to save the record
The initializer configuration will be present only in newly generated Rails 5 apps.
If you are upgrading from an older version of Rails, you can add this initializer yourself to enable this change for entire application.
This is a welcome change in Rails 5 which will help prevent accidental halting of the callbacks.
If this blog was helpful, check out our full blog archive.