MongoDB Mongoid 中的 callback 陷阱

cinic · May 04, 2015 · Last by cinic replied at May 04, 2015 · 7690 hits

在 save 一个实例时,返回了 false,但查看 errors 却是空的,也就是说 validate 都通过了,但却没有保存成功。于是调用了 save!方法,报错:

Mongoid::Errors::Callback: 
Problem:
  Calling save! on User resulted in a false return from a callback.
Summary:
  If a before callback returns false when using Document.create!, Document#save!, or Documnet#update_attributes! this error will get raised since the document did not actually get saved.
Resolution:
  Double check all before callbacks to make sure they are not unintentionally returning false.
    from /Users/cinic/.rvm/gems/ruby-2.1.4/gems/mongoid-4.0.0/lib/mongoid/persistable.rb:93:in `fail_due_to_callback!'
    from /Users/cinic/.rvm/gems/ruby-2.1.4/gems/mongoid-4.0.0/lib/mongoid/persistable/savable.rb:46:in `save!'
    from (irb):6
    from /Users/cinic/.rvm/gems/ruby-2.1.4/gems/railties-4.1.7/lib/rails/commands/console.rb:90:in `start'
    from /Users/cinic/.rvm/gems/ruby-2.1.4/gems/railties-4.1.7/lib/rails/commands/console.rb:9:in `start'
    from /Users/cinic/.rvm/gems/ruby-2.1.4/gems/railties-4.1.7/lib/rails/commands/commands_tasks.rb:69:in `console'
    from /Users/cinic/.rvm/gems/ruby-2.1.4/gems/railties-4.1.7/lib/rails/commands/commands_tasks.rb:40:in `run_command!'
    from /Users/cinic/.rvm/gems/ruby-2.1.4/gems/railties-4.1.7/lib/rails/commands.rb:17:in `<top (required)>'
    from bin/rails:4:in `require'
    from bin/rails:4:in `<main>'

根据报错信息,又去类里查看了所以的 callback 方法,其实只有一个 before_save:

self.is_member = self.point > 0 ? true : false

就是检查某个值大于 0 时,将另一个值设为 true,否则为 false。问题就在这里,因为有可能是返回的 false,所以导致这个 callback 最后的返回值也是 false,最后解决是在这个方法最后加上 return true。有没有碰到过这样问题的,这算是个 Bug 吗?

Mongoid 的文档中有这样说:

Using callbacks for domain logic is a bad design practice, and can lead to unexpected errors that are hard to debug when callbacks in the chain halt execution. It is our recommendation to only use them for cross-cutting concerns, like queueing up background jobs.

是不是不应该这么用 callback 呢?

callback 关联的函数,如果返回了 false,保存都是会失败的,这个在 ActiveRecord 里面也是这样的。 所以你应该确保 callback 相关的函数不会返回 false(别忘了 Ruby 默认最后行的值是作为返回值的)


另外今年的 RubyConf 也有讲过这个事情 Better Callbacks in Rails 5

还给 Rails 提交了新的 callback 处理错误的实现:rails/rails#17227

Current

before_save do
  return false if foo != bar
end

Rails 5 will be

before_save do
  throw :abort if foo != bar
end

嗯,了解!

You need to Sign in before reply, if you don't have an account, please Sign up first.