MongoDB Mongoid 中的 callback 陷阱

cinic · 2015年05月04日 · 最后由 cinic 回复于 2015年05月04日 · 3272 次阅读

在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呢?

共收到 2 条回复

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

嗯,了解!

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册