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

时间:2022-03-19 12:38:14

I'm upgrading a Rails application I've inherited from 3.2 to 4.0.1. I followed and finished the edge guide here:

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

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

I've gotten everything fixed except for a single error that I can't seem to find the root cause of. When I attempt to save a User model object, I'm met with the following error:

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

[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 and rals 4.0.1 are installed. I use chgems and I purged my .gem/ directory and Gemfile.lock before bundling again.

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

Here is a Gist of the User model.

这是用户模型的要点。

And here is all of the backtrace output I could get from pry.

这里是我可以从pry得到的所有回溯输出。

Here is a link to the User table schema.

这是User表架构的链接。

2 个解决方案

#1


7  

Once you've found the offending callback to be this one:

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

  before_create :activate_license

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

things begin to be clearer. The activate_licence is a before callback. Before callbacks can halt the whole callbacks chain by returning false (or raising an exception).

事情开始变得更加清晰。 activate_licence是一个回调之前的。在回调之前,可以通过返回false(或引发异常)来暂停整个回调链。

If we look carefully in the debug output that you provided by manually adding some puts lines into the Rails callbacks code, we can indeed find the comparison of this callback result with false (here - I removed some unimportant parts of the code):

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

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

Because the support for halting before callbacks by returning false (i.e. the Rails code shown above) practically has not changed from Rails 3.2 to Rails 4.0.1, the issue must lie in the comparison itself.

因为通过返回false(即上面显示的Rails代码)在回调之前停止支持实际上没有从Rails 3.2改为Rails 4.0.1,所以问题必须在于比较本身。

The callback returns a DateTime object (it's the last assignment in the method which is also returned). And, indeed, the comparison of DateTimes changed significantly between the two Rails versions (also note that the == operator is normally evaluated using the <=> operator):

回调返回一个DateTime对象(它也是方法中的最后一个赋值,也是返回的)。事实上,DateTimes的比较在两个Rails版本之间发生了显着变化(同时请注意,==运算符通常使用<=>运算符进行评估):

  • in Rails 3.2 it was this:

    在Rails 3.2中就是这样的:

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

    notice especially the respond_to? check if the other object is also a date or time object while otherwise returning nil.

    特别注意respond_to?检查另一个对象是否也是日期或时间对象,否则返回nil。

  • whereas in Rails 4.0.1 this changed to the bare code below:

    而在Rails 4.0.1中,这改为下面的裸代码:

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

    → all sanity checks are gone!

    →所有的理智检查都没了!

Now, everything is clear: the result of the callback (a DateTime object) is compared using the <=> operator with false and under Rails 4.0, the comparison tries to convert the false object to DateTime without any sanity checks, which of course fails and throws the exception.

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

To fix this issue, simply ensure that your callback returns something that Rails can compare with false without any problems, e.g. true, as your callback is never supposed to halt the chain:

要解决此问题,只需确保您的回调返回Rails可以与false进行比较而没有任何问题的内容,例如:是的,因为你的回调永远不应该停止链:

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

Now everything should work as expected again.

现在一切都应该按预期工作了。

#2


2  

You can bind even in core classes, please do something like this and check what the other is, from where it came from.

你甚至可以在核心课程中绑定,请做这样的事情并检查另一个是什么,从哪里来。

/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

#1


7  

Once you've found the offending callback to be this one:

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

  before_create :activate_license

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

things begin to be clearer. The activate_licence is a before callback. Before callbacks can halt the whole callbacks chain by returning false (or raising an exception).

事情开始变得更加清晰。 activate_licence是一个回调之前的。在回调之前,可以通过返回false(或引发异常)来暂停整个回调链。

If we look carefully in the debug output that you provided by manually adding some puts lines into the Rails callbacks code, we can indeed find the comparison of this callback result with false (here - I removed some unimportant parts of the code):

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

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

Because the support for halting before callbacks by returning false (i.e. the Rails code shown above) practically has not changed from Rails 3.2 to Rails 4.0.1, the issue must lie in the comparison itself.

因为通过返回false(即上面显示的Rails代码)在回调之前停止支持实际上没有从Rails 3.2改为Rails 4.0.1,所以问题必须在于比较本身。

The callback returns a DateTime object (it's the last assignment in the method which is also returned). And, indeed, the comparison of DateTimes changed significantly between the two Rails versions (also note that the == operator is normally evaluated using the <=> operator):

回调返回一个DateTime对象(它也是方法中的最后一个赋值,也是返回的)。事实上,DateTimes的比较在两个Rails版本之间发生了显着变化(同时请注意,==运算符通常使用<=>运算符进行评估):

  • in Rails 3.2 it was this:

    在Rails 3.2中就是这样的:

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

    notice especially the respond_to? check if the other object is also a date or time object while otherwise returning nil.

    特别注意respond_to?检查另一个对象是否也是日期或时间对象,否则返回nil。

  • whereas in Rails 4.0.1 this changed to the bare code below:

    而在Rails 4.0.1中,这改为下面的裸代码:

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

    → all sanity checks are gone!

    →所有的理智检查都没了!

Now, everything is clear: the result of the callback (a DateTime object) is compared using the <=> operator with false and under Rails 4.0, the comparison tries to convert the false object to DateTime without any sanity checks, which of course fails and throws the exception.

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

To fix this issue, simply ensure that your callback returns something that Rails can compare with false without any problems, e.g. true, as your callback is never supposed to halt the chain:

要解决此问题,只需确保您的回调返回Rails可以与false进行比较而没有任何问题的内容,例如:是的,因为你的回调永远不应该停止链:

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

Now everything should work as expected again.

现在一切都应该按预期工作了。

#2


2  

You can bind even in core classes, please do something like this and check what the other is, from where it came from.

你甚至可以在核心课程中绑定,请做这样的事情并检查另一个是什么,从哪里来。

/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