before_* callbacks rollback transactions if its method returns false

Public, Troubleshooting, Rails Errors

harrylevine

Created: Oct 05, 2016     Updated: Oct 05, 2016


I had a form, where in one of the fields, the user was clicking a drop-down menu, and choosing between one of three choices.

After the form was submitted, I had a callback that was doing something to the record.

When the user selected option 2, the callback worked; however, if they selected either option 1 or 3, the record kept rolling back.

After trying numerous debugging approaches in the controller, in the model, in the form, and more, nothing revealed why these two selections caused a rollback, while the third selection always worked.

After consulting a colleague on the issue, and both of us being baffled for quite some time, he ultimately uncovered that:

before_* callbacks rollback transactions if its method returns false

Let's take a look at the callback, before and after this revelation.

Initially I had the following before_save callback:

## user_title.rb

  before_save :set_accrues_pto

  ...

  private

  def set_accrues_pto
    case self.time_off_accrual_type
    when NO_ACCRUAL_TYPE
      self.accrues_pto = false
    when PTO_ACCRUAL_TYPE
      self.accrues_pto = true
    when RTO_ACCRUAL_TYPE
      self.accrues_pto = false
    end
  end

Both the self.accrues_pto = false line and the self.accrues_pto = false line was returning false, thereby triggering the before_save callback to rollback the record.

After consulting with another team member, he advised us that:

It's a good practice to always explicitly return from callbacks (if not intending to rollback), to avoid encountering these head-scratchers.

Based on that advice, here is the updated callback method, which works as desired:

  private

  def set_accrues_pto
    case self.time_off_accrual_type
    when NO_ACCRUAL_TYPE
      self.accrues_pto = false
    when PTO_ACCRUAL_TYPE
      self.accrues_pto = true
    when RTO_ACCRUAL_TYPE
      self.accrues_pto = false
    end

    return
  end

You'll note the explicit return at the end of the method.

This same teammate then went onto to advise us that:

The only significant return value of callbacks is false (Rails will cancel the rest of the callbacks). Other values returned from callbacks, including nil (which is returned when you just do return) don't trigger any special processing.