mattmattmatt
mattmattmatt

Reputation: 965

Comparing identical DateTime objects in ruby -- why are these two DateTime.now's not equal?

In a rails app, I have model code of the form:

def do_stuff(resource)
  models = Model.where(resource: resource)
  operated_at = DateTime.now

  models.each { |model| some_operation(model, operated_at) }

  some_other_operation models, operated_at
end

def some_operation(model, operated_at)
  model.date_time_field = operated_at
  model.save
end

def some_other_operation(models, operated_at)
  models.each do |model|
    if model.date_time_field < operated_at
      # do something
    end    
  end
end

The 'do something' block of some_other_operation will always be executed, in this situation. Even stranger, if I set this up in the console without all the defined functions, the comparison works as expected. Example:

> model = Model.first
> time_var = DateTime.now
> model.last_imported_at = time_var
=> Fri, 25 Oct 2013 21:14:06 +0000
> model.save
=> true
> model.last_imported_at < time_var
=> false
> model.last_imported_at == time_var
=> true

Finally, if, in some_other_operation I instead compare in the following way:

if model.date_time_field.to_i < operated_at.to_i
  # do something
end

the 'do something' block is reached only when expected. I suspect this is because the to_i method will drop the fractions of a second defined on the DateTime object, and the operated_at variable is actually being redefined as DateTime.now for each methods' scope. If this is the case, then I guess my question is how do I force operated_at to not be redefined for each scope?

Upvotes: 4

Views: 2935

Answers (4)

struthersneil
struthersneil

Reputation: 2750

Possible clue; the act of saving and reloading truncates the seconds_fraction part of the DateTime. The date field becomes an instance of ActiveSupport::TimeWithZone. Just saving without reloading doesn't do this; the real DateTime object is still there.

2.0.0-p195 :001 > dt            =  DateTest.create
2.0.0-p195 :002 > right_now     =  DateTime.now
2.0.0-p195 :004 > dt.created_at =  right_now
2.0.0-p195 :005 > dt.created_at == right_now
 => true 

2.0.0-p195 :006 > dt.save
2.0.0-p195 :007 > dt.created_at == right_now
 => true 

2.0.0-p195 :008 > dt = DateTest.find(dt.id)
2.0.0-p195 :009 > dt.created_at == right_now
 => false 

Edit: of course calling models.each is going to load the models there and then because of the lazy loading behaviour. Props to the other answerer. As an experiment, try setting models to Model.where(resource: resource).to_a.

Upvotes: 3

Ruby_Pry
Ruby_Pry

Reputation: 237

You have three separate methods defined above with three separate operated_at local variables. Local variables are limited to the scope of the method which defines them.

You need to define instance variables, which persist throughout a class. For example, you could:

def Model
  attr_accessor :operated_at

  def do_stuff(resource)
    models = Model.where(resource: resource)
    operated_at = DateTime.now

    models.each { |model| some_operation(model, operated_at) }

    some_other_operation models, operated_at
  end

  def some_operation(model, operated_at)
    model.date_time_field = operated_at
    model.save
  end

  def some_other_operation(models, operated_at)
    models.each do |model|
      if model.date_time_field < operated_at
        # do something
      end    
    end
  end
end  

This would enable you to access operated_at throughout all of the class methods.

Upvotes: 1

Eric
Eric

Reputation: 1

DateTime.now doesn't work for comparison with Database datetime object.

Example:

  1. If you have the server running in Pacific Time Zone, you will expect to get the

    DateTime.now --> Fri, 25 Oct 2013 15:28:21 -0800

(Daylight Saving Time in October supposed to be -7 hour from UTC, but somehow the datetime.now always give you -8 from UTC)

  1. I think if you use Time.zone.now instead of DateTime.now will give you the correct comparison.

    Time.zone.now --> Fri, 25 Oct 2013 15:27:17 PDT -07:00

Upvotes: 0

phoet
phoet

Reputation: 18835

I assume that this problem is due to the lazy scope that you use: models = Model.where(resource: resource)

models is a proxy collection and will be resolved by rails at some point and might be re-evaluated without you knowing exactly.

so when you change an attribute and you don't reload the object before checking a property it might not be up to date.

Upvotes: 0

Related Questions