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

2

9

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.

Pottery answered 23/4, 2016 at 1:37 Comment(18)
it happen because of created_at and updated_atAirmail
@Airmail would you mind explaining further? Thanks!Pottery
can you show the schema for users table. Also have you restarted the server, can you try creating a user in consoleVirtue
the link for the gist with the backtrace output is giving a 404 errorJulenejulep
@Pottery can u post your schema for user table ?Pederasty
From the stack trace it is evident that a callback (probably a before_create one) is trying to compare some dates but one of them is false. But it is a mystery to me what callback it is. Probably it's not one of those in user model but more likely a callback from a gem, perhaps devise, or something else?Foretopgallant
What I would do is to open up the active_support/callbacks.rb file in ActiveSupport on this line (see your stack trace for the precise file location) and add a debug line before it: Rails.logger.debug "#{name} #{str}". This should reveal the callbacks internal name and the callbacks code. Then please paste here the debug that is printed for the callback with the same name as in your stack trace: _run__768037688__create__callbacks.Foretopgallant
@Virtue this was user creation from the console.Pottery
@BoraMa great observation. I'll give that a try, thanks.Pottery
@BoraMa here's the requested info: gist.github.com/cmhobbs/cbb92fcd9114005f5e3c05363722d546Pottery
That's odd... Feels like your DB has different schema rather than specified in schema.rb, but that's just a wild guess. Try to remove (comment out) pieces of code until you get smaller example to achieve the error - there's a big chance that you will find bugging piece of code pretty fast. Also, unrelated, but avoid Time.now and use Time.current (shortcut for Time.zone.now), or you might get into trouble :)Clupeid
If you update Rails to say 4.2-ish, this problem should go away. That said, looking at the root of the problem, it sounds like maybe your User table has a default on created_at and updated_at columns set to false ... which, as other posters said, makes no sense.Fabozzi
@Fabozzi my fear with upgrading another two rails versions is the introduction of further problems. How do I know if the default on created_at and updated_at is set to false and how do I change that to the recommended default? Thanks!Pottery
Well, you'd be in the same boat as you are now :) Far as checking, if you're using postgres, you should be able to do psql -c "\d+ users" {database}. You can see what the makeup of the table is. But, again, doubtful (but post here if that is somehow the case!). Most likely, perhaps, is that one of your gems is doing something odd. One step may be to strip out extraneous gems, get the create command working, then re-introduce them one by one until you break again. If you post your Gemfile here, maybe someone can guide/shortcut that process.Fabozzi
not to be trollish but have you tried writing a unit test for the User class? Something that would just run the "valid?" or "save" command should be enough to flesh out if this is in the code or there is bad data in the database.Interne
My guess is there's a problem with the callbacks. Comment out the callbacks and see if it works.Ejectment
The offending line was this one: gist.github.com/cmhobbs/… The comment by @BoraMa led to me finding it. I'm still not sure how to fix it but that particular method feature seems to be deprecated, so I simply commented it out. I'm not sure what the best route for submitting/accepting an answer would be at this point.Pottery
Ah, great that you found it, I think I get it now! Will post an explanation in an answer.Foretopgallant
J
9

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 DateTimes 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.

Jebel answered 7/5, 2016 at 5:55 Comment(0)
B
3

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
Burgoyne answered 29/4, 2016 at 12:48 Comment(1)
With overriding this in gem file, it works -> do we have any solution without using this ?Scopophilia

© 2022 - 2024 — McMap. All rights reserved.