Reputation: 965
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
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
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
Reputation: 1
DateTime.now doesn't work for comparison with Database datetime object.
Example:
DateTime.now --> Fri, 25 Oct 2013 15:28:21 -0800
Time.zone.now --> Fri, 25 Oct 2013 15:27:17 PDT -07:00
Upvotes: 0
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