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.