1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00
rails--rails/activerecord/test/cases/timestamp_test.rb
Yasuo Honda 334d5240b4 Move test_index_is_created_for_both_timestamps to TimestampsWithoutTransactionTest
This commit addresses these failures when the backend RDBMS executes implicit commit for DDL like MySQL and Oracle.

```ruby
$ ARCONN=mysql2 bin/test test/cases/integration_test.rb test/cases/timestamp_test.rb --seed 58225 -n '/^(?:TimestampTest#(?:test_index_is_created_for_both_timestamps)|IntegrationTest#(?:test_cache_key_for_newer_updated_at|test_cache_key_format_for_existing_record_with_updated_at|test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format))$/' -v
Using mysql2
Run options: --seed 58225 -n "/^(?:TimestampTest#(?:test_index_is_created_for_both_timestamps)|IntegrationTest#(?:test_cache_key_for_newer_updated_at|test_cache_key_format_for_existing_record_with_updated_at|test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format))$/" -v

TimestampTest#test_index_is_created_for_both_timestamps = 0.19 s = .
IntegrationTest#test_cache_key_format_for_existing_record_with_updated_at = 0.05 s = F
IntegrationTest#test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format = 0.02 s = F
IntegrationTest#test_cache_key_for_newer_updated_at = 0.01 s = F

Finished in 0.272880s, 14.6585 runs/s, 14.6585 assertions/s.

  1) Failure:
IntegrationTest#test_cache_key_format_for_existing_record_with_updated_at [/home/yahonda/git/rails/activerecord/test/cases/integration_test.rb:111]:
--- expected
+++ actual
@@ -1 +1 @@
-"developers/1-20170717135609430848"
+"developers/1-20170817135609283207"

  2) Failure:
IntegrationTest#test_cache_key_format_for_existing_record_with_updated_at_and_custom_cache_timestamp_format [/home/yahonda/git/rails/activerecord/test/cases/integration_test.rb:116]:
--- expected
+++ actual
@@ -1 +1 @@
-"cached_developers/1-20170717135609"
+"cached_developers/1-20170817135609"

  3) Failure:
IntegrationTest#test_cache_key_for_newer_updated_at [/home/yahonda/git/rails/activerecord/test/cases/integration_test.rb:147]:
--- expected
+++ actual
@@ -1 +1 @@
-"developers/1-20170717145609430848"
+"developers/1-20170817135609283207"

4 runs, 4 assertions, 3 failures, 0 errors, 0 skips
$
```

`ActiveRecord::TestCase::TimestampTest#test_index_is_created_for_both_timestamps`
calls`ActiveRecord::TestCase::TimestampTest#setup` which updates `updated_at` column value.

```ruby
    @developer = Developer.first
    ... snip ...
    @developer.update_columns(updated_at: Time.now.prev_month)
```

This transaction expected to be rolled back, but if the backend RDBMS like MySQL perfomes
implicit commit when DDL executed, this transacion is committed by `create table` statement
inside `ActiveRecord::TestCase::TimestampTest#test_index_is_created_for_both_timestamps`.

It causes these failures above which expect `update_at` column value are not changed(rollbacked).
2017-08-17 14:12:36 +00:00

478 lines
13 KiB
Ruby

# frozen_string_literal: true
require "cases/helper"
require "support/ddl_helper"
require "models/developer"
require "models/computer"
require "models/owner"
require "models/pet"
require "models/toy"
require "models/car"
require "models/task"
class TimestampTest < ActiveRecord::TestCase
fixtures :developers, :owners, :pets, :toys, :cars, :tasks
def setup
@developer = Developer.first
@owner = Owner.first
@developer.update_columns(updated_at: Time.now.prev_month)
@previously_updated_at = @developer.updated_at
end
def test_saving_a_changed_record_updates_its_timestamp
@developer.name = "Jack Bauer"
@developer.save!
assert_not_equal @previously_updated_at, @developer.updated_at
end
def test_saving_a_unchanged_record_doesnt_update_its_timestamp
@developer.save!
assert_equal @previously_updated_at, @developer.updated_at
end
def test_touching_a_record_updates_its_timestamp
previous_salary = @developer.salary
@developer.salary = previous_salary + 10000
@developer.touch
assert_not_equal @previously_updated_at, @developer.updated_at
assert_equal previous_salary + 10000, @developer.salary
assert @developer.salary_changed?, "developer salary should have changed"
assert @developer.changed?, "developer should be marked as changed"
@developer.reload
assert_equal previous_salary, @developer.salary
end
def test_touching_a_record_with_default_scope_that_excludes_it_updates_its_timestamp
developer = @developer.becomes(DeveloperCalledJamis)
developer.touch
assert_not_equal @previously_updated_at, developer.updated_at
developer.reload
assert_not_equal @previously_updated_at, developer.updated_at
end
def test_saving_when_record_timestamps_is_false_doesnt_update_its_timestamp
Developer.record_timestamps = false
@developer.name = "John Smith"
@developer.save!
assert_equal @previously_updated_at, @developer.updated_at
ensure
Developer.record_timestamps = true
end
def test_saving_when_instance_record_timestamps_is_false_doesnt_update_its_timestamp
@developer.record_timestamps = false
assert Developer.record_timestamps
@developer.name = "John Smith"
@developer.save!
assert_equal @previously_updated_at, @developer.updated_at
end
def test_touching_updates_timestamp_with_given_time
previously_updated_at = @developer.updated_at
new_time = Time.utc(2015, 2, 16, 0, 0, 0)
@developer.touch(time: new_time)
assert_not_equal previously_updated_at, @developer.updated_at
assert_equal new_time, @developer.updated_at
end
def test_touching_an_attribute_updates_timestamp
previously_created_at = @developer.created_at
travel(1.second) do
@developer.touch(:created_at)
end
assert !@developer.created_at_changed? , "created_at should not be changed"
assert !@developer.changed?, "record should not be changed"
assert_not_equal previously_created_at, @developer.created_at
assert_not_equal @previously_updated_at, @developer.updated_at
end
def test_touching_an_attribute_updates_it
task = Task.first
previous_value = task.ending
task.touch(:ending)
now = Time.now.change(usec: 0)
assert_not_equal previous_value, task.ending
assert_in_delta now, task.ending, 1
end
def test_touching_an_attribute_updates_timestamp_with_given_time
previously_updated_at = @developer.updated_at
previously_created_at = @developer.created_at
new_time = Time.utc(2015, 2, 16, 4, 54, 0)
@developer.touch(:created_at, time: new_time)
assert_not_equal previously_created_at, @developer.created_at
assert_not_equal previously_updated_at, @developer.updated_at
assert_equal new_time, @developer.created_at
assert_equal new_time, @developer.updated_at
end
def test_touching_many_attributes_updates_them
task = Task.first
previous_starting = task.starting
previous_ending = task.ending
task.touch(:starting, :ending)
now = Time.now.change(usec: 0)
assert_not_equal previous_starting, task.starting
assert_not_equal previous_ending, task.ending
assert_in_delta now, task.starting, 1
assert_in_delta now, task.ending, 1
end
def test_touching_a_record_without_timestamps_is_unexceptional
assert_nothing_raised { Car.first.touch }
end
def test_touching_a_no_touching_object
Developer.no_touching do
assert @developer.no_touching?
assert !@owner.no_touching?
@developer.touch
end
assert !@developer.no_touching?
assert !@owner.no_touching?
assert_equal @previously_updated_at, @developer.updated_at
end
def test_touching_related_objects
@owner = Owner.first
@previously_updated_at = @owner.updated_at
Owner.no_touching do
@owner.pets.first.touch
end
assert_equal @previously_updated_at, @owner.updated_at
end
def test_global_no_touching
ActiveRecord::Base.no_touching do
assert @developer.no_touching?
assert @owner.no_touching?
@developer.touch
end
assert !@developer.no_touching?
assert !@owner.no_touching?
assert_equal @previously_updated_at, @developer.updated_at
end
def test_no_touching_threadsafe
Thread.new do
Developer.no_touching do
assert @developer.no_touching?
sleep(1)
end
end
assert !@developer.no_touching?
end
def test_no_touching_with_callbacks
klass = Class.new(ActiveRecord::Base) do
self.table_name = "developers"
attr_accessor :after_touch_called
after_touch do |user|
user.after_touch_called = true
end
end
developer = klass.first
klass.no_touching do
developer.touch
assert_not developer.after_touch_called
end
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
pet = Pet.first
owner = pet.owner
previously_owner_updated_at = owner.updated_at
travel(1.second) do
pet.name = "Fluffy the Third"
pet.save
end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
def test_destroying_a_record_with_a_belongs_to_that_specifies_touching_the_parent_should_update_the_parent_updated_at
pet = Pet.first
owner = pet.owner
previously_owner_updated_at = owner.updated_at
travel(1.second) do
pet.destroy
end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
def test_saving_a_new_record_belonging_to_invalid_parent_with_touch_should_not_raise_exception
klass = Class.new(Owner) do
def self.name; "Owner"; end
validate { errors.add(:base, :invalid) }
end
pet = Pet.new(owner: klass.new)
pet.save!
assert pet.owner.new_record?
end
def test_saving_a_record_with_a_belongs_to_that_specifies_touching_a_specific_attribute_the_parent_should_update_that_attribute
klass = Class.new(ActiveRecord::Base) do
def self.name; "Pet"; end
belongs_to :owner, touch: :happy_at
end
pet = klass.first
owner = pet.owner
previously_owner_happy_at = owner.happy_at
pet.name = "Fluffy the Third"
pet.save
assert_not_equal previously_owner_happy_at, pet.owner.happy_at
end
def test_touching_a_record_with_a_belongs_to_that_uses_a_counter_cache_should_update_the_parent
klass = Class.new(ActiveRecord::Base) do
def self.name; "Pet"; end
belongs_to :owner, counter_cache: :use_count, touch: true
end
pet = klass.first
owner = pet.owner
owner.update_columns(happy_at: 3.days.ago)
previously_owner_updated_at = owner.updated_at
travel(1.second) do
pet.name = "I'm a parrot"
pet.save
end
assert_not_equal previously_owner_updated_at, pet.owner.updated_at
end
def test_touching_a_record_touches_parent_record_and_grandparent_record
klass = Class.new(ActiveRecord::Base) do
def self.name; "Toy"; end
belongs_to :pet, touch: true
end
toy = klass.first
pet = toy.pet
owner = pet.owner
time = 3.days.ago
owner.update_columns(updated_at: time)
toy.touch
owner.reload
assert_not_equal time, owner.updated_at
end
def test_touching_a_record_touches_polymorphic_record
klass = Class.new(ActiveRecord::Base) do
def self.name; "Toy"; end
end
wheel_klass = Class.new(ActiveRecord::Base) do
def self.name; "Wheel"; end
belongs_to :wheelable, polymorphic: true, touch: true
end
toy = klass.first
time = 3.days.ago
toy.update_columns(updated_at: time)
wheel = wheel_klass.new
wheel.wheelable = toy
wheel.save
wheel.touch
assert_not_equal time, toy.updated_at
end
def test_changing_parent_of_a_record_touches_both_new_and_old_parent_record
klass = Class.new(ActiveRecord::Base) do
def self.name; "Toy"; end
belongs_to :pet, touch: true
end
toy1 = klass.find(1)
old_pet = toy1.pet
toy2 = klass.find(2)
new_pet = toy2.pet
time = 3.days.ago.at_beginning_of_hour
old_pet.update_columns(updated_at: time)
new_pet.update_columns(updated_at: time)
toy1.pet = new_pet
toy1.save!
old_pet.reload
new_pet.reload
assert_not_equal time, new_pet.updated_at
assert_not_equal time, old_pet.updated_at
end
def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_within_same_class
car_class = Class.new(ActiveRecord::Base) do
def self.name; "Car"; end
end
wheel_class = Class.new(ActiveRecord::Base) do
def self.name; "Wheel"; end
belongs_to :wheelable, polymorphic: true, touch: true
end
car1 = car_class.find(1)
car2 = car_class.find(2)
wheel = wheel_class.create!(wheelable: car1)
time = 3.days.ago.at_beginning_of_hour
car1.update_columns(updated_at: time)
car2.update_columns(updated_at: time)
wheel.wheelable = car2
wheel.save!
assert_not_equal time, car1.reload.updated_at
assert_not_equal time, car2.reload.updated_at
end
def test_changing_parent_of_a_record_touches_both_new_and_old_polymorphic_parent_record_changes_with_other_class
car_class = Class.new(ActiveRecord::Base) do
def self.name; "Car"; end
end
toy_class = Class.new(ActiveRecord::Base) do
def self.name; "Toy"; end
end
wheel_class = Class.new(ActiveRecord::Base) do
def self.name; "Wheel"; end
belongs_to :wheelable, polymorphic: true, touch: true
end
car = car_class.find(1)
toy = toy_class.find(3)
wheel = wheel_class.create!(wheelable: car)
time = 3.days.ago.at_beginning_of_hour
car.update_columns(updated_at: time)
toy.update_columns(updated_at: time)
wheel.wheelable = toy
wheel.save!
assert_not_equal time, car.reload.updated_at
assert_not_equal time, toy.reload.updated_at
end
def test_clearing_association_touches_the_old_record
klass = Class.new(ActiveRecord::Base) do
def self.name; "Toy"; end
belongs_to :pet, touch: true
end
toy = klass.find(1)
pet = toy.pet
time = 3.days.ago.at_beginning_of_hour
pet.update_columns(updated_at: time)
toy.pet = nil
toy.save!
pet.reload
assert_not_equal time, pet.updated_at
end
def test_timestamp_column_values_are_present_in_the_callbacks
klass = Class.new(ActiveRecord::Base) do
self.table_name = "people"
before_create do
self.born_at = created_at
end
end
person = klass.create first_name: "David"
assert_not_equal person.born_at, nil
end
def test_timestamp_attributes_for_create_in_model
toy = Toy.first
assert_equal ["created_at"], toy.send(:timestamp_attributes_for_create_in_model)
end
def test_timestamp_attributes_for_update_in_model
toy = Toy.first
assert_equal ["updated_at"], toy.send(:timestamp_attributes_for_update_in_model)
end
def test_all_timestamp_attributes_in_model
toy = Toy.first
assert_equal ["created_at", "updated_at"], toy.send(:all_timestamp_attributes_in_model)
end
end
class TimestampsWithoutTransactionTest < ActiveRecord::TestCase
include DdlHelper
self.use_transactional_tests = false
class TimestampAttributePost < ActiveRecord::Base
attr_accessor :created_at, :updated_at
end
def test_do_not_write_timestamps_on_save_if_they_are_not_attributes
with_example_table ActiveRecord::Base.connection, "timestamp_attribute_posts", "id integer primary key" do
post = TimestampAttributePost.new(id: 1)
post.save! # should not try to assign and persist created_at, updated_at
assert_nil post.created_at
assert_nil post.updated_at
end
end
def test_index_is_created_for_both_timestamps
ActiveRecord::Base.connection.create_table(:foos, force: true) do |t|
t.timestamps null: true, index: true
end
indexes = ActiveRecord::Base.connection.indexes("foos")
assert_equal ["created_at", "updated_at"], indexes.flat_map(&:columns).sort
ensure
ActiveRecord::Base.connection.drop_table(:foos)
end
end