Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Fix #6975. Round usec when writing timestamp attribute. #6986

Closed
wants to merge 1 commit into from

4 participants

@kennyj
Collaborator

Please see #6975.
I think we should also merge to 3-2-stable.

cc/ @tenderlove @rafaelfranca

@rafaelfranca
Owner

@tenderlove could you review this one?

...tive_record/attribute_methods/time_zone_conversion.rb
@@ -61,11 +61,14 @@ def #{attr_name}=(original_time)
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- time = time.in_time_zone rescue nil if time
- changed = read_attribute(:#{attr_name}) != time
- write_attribute(:#{attr_name}, original_time)
- #{attr_name}_will_change! if changed
- @attributes_cache["#{attr_name}"] = time
+ time = round_usec(time.in_time_zone) rescue nil if time
+ attr = round_usec(read_attribute(:#{attr_name}))
+ if !time || !attr || attr != time
+ original = original_time.acts_like?(:time) ? round_usec(original_time) : original_time
+ write_attribute(:#{attr_name}, original)
@rafaelfranca Owner

I think we should not write the rounded time in the database, we should only round they to compare. WDYT?

@kennyj Collaborator
kennyj added a note

I can't judge it.
IMHO, that behavior is a little tricky...

@rafaelfranca Owner

Could you try to write the not rounded time in the database and in the attributes_cache and see if the tests will still pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kennyj
Collaborator

@rafaelfranca I updated this PR. All tests are green.

...tive_record/attribute_methods/time_zone_conversion.rb
@@ -59,11 +59,14 @@ def #{attr_name}=(original_time)
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- time = time.in_time_zone rescue nil if time
- changed = read_attribute(:#{attr_name}) != time
- write_attribute(:#{attr_name}, original_time)
- #{attr_name}_will_change! if changed
- @attributes_cache["#{attr_name}"] = time
+ zoned_time = time ? time.in_time_zone : nil rescue nil
+ rounded_time = round_usec(zoned_time)
+ attr = round_usec(read_attribute(:#{attr_name}))
@tenderlove Owner

Don't convert to a symbol here, it just gets converted back to a string.

@rafaelfranca Owner

maybe rounded_current_value or something like this is a better name.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tive_record/attribute_methods/time_zone_conversion.rb
@@ -59,11 +59,14 @@ def #{attr_name}=(original_time)
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- time = time.in_time_zone rescue nil if time
- changed = read_attribute(:#{attr_name}) != time
- write_attribute(:#{attr_name}, original_time)
- #{attr_name}_will_change! if changed
- @attributes_cache["#{attr_name}"] = time
+ zoned_time = time ? time.in_time_zone : nil rescue nil
@tenderlove Owner

I think this can be time && time.in_time_zone rescue nil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tive_record/attribute_methods/time_zone_conversion.rb
@@ -79,6 +82,11 @@ def create_time_zone_conversion_attribute?(name, column)
[:datetime, :timestamp].include?(column.type)
end
end
+
+ private
+ def round_usec(value)
+ !value ? nil : value.change(:usec => 0)
@tenderlove Owner

Also value && value.change(:usec => 0), thought it might be more clear to just do:

return unless value
value.change(:usec => 0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
...tive_record/attribute_methods/time_zone_conversion.rb
@@ -59,11 +59,14 @@ def #{attr_name}=(original_time)
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- time = time.in_time_zone rescue nil if time
- changed = read_attribute(:#{attr_name}) != time
- write_attribute(:#{attr_name}, original_time)
- #{attr_name}_will_change! if changed
- @attributes_cache["#{attr_name}"] = time
+ zoned_time = time ? time.in_time_zone : nil rescue nil
+ rounded_time = round_usec(zoned_time)
+ attr = round_usec(read_attribute(:#{attr_name}))
+ if !rounded_time || !attr || attr != rounded_time
@rafaelfranca Owner

I think here should be:

if (attr != rounded_time) || (!attr && original_time)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kennyj
Collaborator

@tenderlove @rafaelfranca Thanks for many comments. I rebased and fixed some codes.

@sferik

I have a concern that this patch does not do what it claims. In my testing, it seems to round times to the nearest whole second, not the nearest microsecond. Since this was merged into 3-2-stable, it has created an incompatibility between versions 3.2.8 and 3.2.9. :frowning:

@rafaelfranca

@sferik the data is not changed, we round the value only to compare.

Yes we round to the nearest second because MySQL already does this when store the value.

This behavior in 3.2.8 was buggy since 3.2.6 and now it seems fixed, but if you think it is wrong please give us some way to reproduce your issue.

@sferik

If this behavior is specific to MySQL, doesn't it belong in the MySQL adapter?

Here is an example of the problems caused by this change:

class Parent < ActiveRecord::Base
  has_one :child
end

class Child < ActiveRecord::Base
  belongs_to :parent, :touch => true
end

setup do
  @parent = Parent.create
  Child.create(:parent => @parent)
end

def test_cache_key_changes_when_child_touched
  key = @parent.cache_key
  @parent.child.touch
  @parent.reload
  assert_not_equal key, @parent.cache_key # F
end

The only way I can make this test pass (without monkey patching) is by adding sleep 1 before @object.child.touch. :disappointed:

@rafaelfranca

@sferik now I see your problem. Could you open a new issue? I'll investigate

@pixeltrix pixeltrix referenced this pull request from a commit
@pixeltrix pixeltrix Revert "Merge pull request #6986 from kennyj/fix_6975"
This reverts commit 8905c1f.

Closes #8460

Conflicts:
	activerecord/test/cases/dirty_test.rb
97a4db9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 21, 2012
  1. @kennyj
This page is out of date. Refresh to see the latest.
View
19 activerecord/lib/active_record/attribute_methods/time_zone_conversion.rb
@@ -59,11 +59,14 @@ def #{attr_name}=(original_time)
unless time.acts_like?(:time)
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
end
- time = time.in_time_zone rescue nil if time
- changed = read_attribute(:#{attr_name}) != time
- write_attribute(:#{attr_name}, original_time)
- #{attr_name}_will_change! if changed
- @attributes_cache["#{attr_name}"] = time
+ zoned_time = time && time.in_time_zone rescue nil
+ rounded_time = rounded_current_value(zoned_time)
+ attr = rounded_current_value(read_attribute("#{attr_name}"))
+ if (attr != rounded_time) || (!attr && original_time)
+ write_attribute("#{attr_name}", original_time)
+ #{attr_name}_will_change!
+ @attributes_cache["#{attr_name}"] = zoned_time
+ end
end
EOV
generated_attribute_methods.module_eval(method_body, __FILE__, line)
@@ -79,6 +82,12 @@ def create_time_zone_conversion_attribute?(name, column)
[:datetime, :timestamp].include?(column.type)
end
end
+
+ private
+ def rounded_current_value(value)
+ return unless value
+ value.change(:usec => 0)
+ end
end
end
end
View
15 activerecord/test/cases/dirty_test.rb
@@ -525,6 +525,21 @@ def test_field_named_field
end
end
+ def test_setting_time_attributes_with_time_zone_field_to_same_time_should_not_be_marked_as_a_change
+ in_time_zone 'Paris' do
+ target = Class.new(ActiveRecord::Base)
+ target.table_name = 'pirates'
+
+ created_on = Time.now
+
+ pirate = target.create(:created_on => created_on)
+ pirate.reload # Here mysql truncate the usec value to 0
+
+ pirate.created_on = created_on
+ assert !pirate.created_on_changed?
+ end
+ end
+
private
def with_partial_updates(klass, on = true)
old = klass.partial_updates?
Something went wrong with that request. Please try again.