Rails 3.2 到 4.0 升级:false:FalseClass 的未定义方法 to_datetime

Rails 3.2 to 4.0 Upgrade: Undefined method to_datetime for false:FalseClass

我正在将我从 3.2 继承的 Rails 应用程序升级到 4.0.1。我在这里完成了边缘指南:

http://edgeguides.rubyonrails.org/upgrading_ruby_on_rails.html#upgrading-from-rails-3-2-to-rails-4-0

除了一个我似乎找不到根本原因的错误外,我已经修复了所有问题。当我尝试保存用户模型对象时,遇到以下错误:

[1] pry(main)> User.create(name: "test user", email: "testuser@frobnitz.com", password: "testPassword123", password_confirmation: "testPassword123")                                                                                                                               

(0.6ms)  BEGIN
(0.9ms)  ROLLBACK
NoMethodError: undefined method `to_datetime' for false:FalseClass
from /home/cmhobbs/src/serve2perform/.gem/ruby/2.3.0/gems/activesupport-4.0.1/lib/active_support/core_ext/date_time/calculations.rb:161:in `<=>'

activesupport 4.0.1 和 rals 4.0.1 已安装。我使用 chgems 并在再次捆绑之前清除了我的 .gem/ 目录和 Gemfile.lock

这是一个Gist of the User model.

here is all of the backtrace output我可以从pry.

这是一个link to the User table schema.

您甚至可以在核心 类 中绑定,请像这样检查 other 是什么,它来自哪里。

/home/cmhobbs/src/serve2perform/.gem/ruby/2.3.0/gems/activesupport-4.0.1/lib/active_support/core_ext/date_time/calculations.rb

def <=>(other)
  binding.pry
  if other.kind_of?(Infinity)
    super
  elsif other.respond_to? :to_datetime
    super other.to_datetime rescue nil
  else
    nil
  end
end

一旦您发现有问题的回调是这个回调:

  before_create :activate_license

  def activate_license
    self.active_license = true
    self.licensed_date = Time.now
  end

事情开始变得更加清晰。 activate_licencebefore 回调。 Before 回调可以 halt the whole callbacks chain by returning false(或引发异常)。

如果我们仔细查看您通过在 Rails 回调代码中手动添加一些 puts 行提供的调试输出,我们确实可以找到此回调结果与 false (here - 我删除了代码中一些不重要的部分):

result = activate_license
halted = (result == false)
if halted
  halted_callback_hook(":activate_license")
end 

因为通过返回 false(即上面显示的 Rails 代码)支持在回调之前停止,实际上与 Rails 3.2 to Rails 4.0.1 没有变化,问题一定出在比较本身.

回调 returns 一个 DateTime 对象(它是方法中的最后一个赋值,它也被返回)。而且,事实上,DateTimes 的比较在两个 Rails 版本之间发生了显着变化(还要注意 == 运算符通常是 evaluated using the <=> operator):

  • 在 Rails 3.2 中是 this:

    def <=>(other)
      if other.kind_of?(Infinity)
        super
      elsif other.respond_to? :to_datetime
       super other.to_datetime
      else
        nil
      end
    end
    

    特别注意 respond_to? 检查 other 对象是否也是日期或时间对象,否则返回 nil.

  • 而在 Rails 4.0.1 中这个 changed to 下面的裸代码:

    def <=>(other)
      super other.to_datetime
    end
    

    → 所有 完整性检查都消失了

现在,一切都清楚了:在 Rails 4.0 下,使用 <=> 运算符与 false 比较回调的结果(DateTime 对象),比较尝试将 false 对象转换为 DateTime 而不进行任何健全性检查,这当然会失败并引发异常。

要解决此问题,只需确保您的回调 returns Rails 可以与 false 相比没有任何问题,例如true,因为你的回调永远不会停止链:

  def activate_license
    self.active_license = true
    self.licensed_date = Time.now
    true
  end

现在一切都应该再次按预期运行。