Rails ActiveRecord Callback 的返回值

Rails 提供的约定非常强大而且灵活, 但是需要熟练掌握和小心使用. 最近我就由于没注意 active-record callback 的一个约定而中枪.

问题的现象是当保存一个 model 的对象时, 结果被 rollback 并且抛出了 ActiveRecord::RecordInvalid 错误. 当我查看这个对象的 errors 时, 发现并没有错误, valid? 也返回 true.

最终发现是由于这个 model 的一个 before_create callback 无意中返回了 false 也导致对象保存失败. Rails 官方的 guides描述如下:

The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.

就是说, 如果某个 before-callback 返回 false 或者抛出异常, 就会立即中断其他还未执行的 callback 以及保存对象的操作. 而我的那个 before-callback 类似于:

before_create :init_attributes

private

def init_attributes
  self.processed = false if self.processed.nil?
end

这个 callback 的作用是在新建对象时, 如果没有给 processed 赋值就设置默认值为 false. 但在 Ruby 中, 一个方法的最后一个表达式就是方法的返回值, 所以这个 callback 在设置默认值的同时返回了 false, 从而导致了对象在没有任何错误的情况下也无法保存.

解决方法: 在 callback 的最后添加 return nil 就 OK 了. 对于这种需要设置默认值的情况, 可以使用 default_value_for 这个 gem.