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.

Improvements in Rails 5

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

Opting out of this behavior

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

How older applications will work with this change?

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.