I'm upgrading a Rails application I've inherited from 3.2 to 4.0.1. I followed and finished the edge guide here:
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: "[email protected]", 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.
Here is a Gist of the User model.
And here is all of the backtrace output I could get from pry
.
Here is a link to the User table schema.
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).
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):
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.
The callback returns a DateTime
object (it's the last assignment in the method which is also returned). And, indeed, the comparison of DateTime
s changed significantly between the two Rails versions (also note that the ==
operator is normally evaluated using the <=>
operator):
in Rails 3.2 it was this:
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
.
whereas in Rails 4.0.1 this changed to the bare code below:
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.
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:
def activate_license
self.active_license = true
self.licensed_date = Time.now
true
end
Now everything should work as expected again.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With