From b19c4eff47d912ee3a038a6e0653eea7df5b67b8 Mon Sep 17 00:00:00 2001 From: Eugene Kenny Date: Mon, 13 Mar 2017 02:01:18 +0000 Subject: Sync transaction state when accessing primary key If a record is modified inside a transaction, it must check the outcome of that transaction before accessing any state which would no longer be valid if it was rolled back. For example, consider a new record that was saved inside a transaction which was later rolled back: it should be restored to its previous state so that saving it again inserts a new row into the database instead of trying to update a row that no longer exists. The `id` and `id=` methods defined on the PrimaryKey module implement this correctly, but when a model uses a custom primary key, the reader and writer methods for that attribute must check the transaction state too. The `read_attribute` and `write_attribute` methods also need to check the transaction state when accessing the primary key. --- activerecord/test/cases/transactions_test.rb | 52 ++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 3 deletions(-) (limited to 'activerecord/test') diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb index 76a997ba8b..a5348e2825 100644 --- a/activerecord/test/cases/transactions_test.rb +++ b/activerecord/test/cases/transactions_test.rb @@ -684,7 +684,7 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end - assert_nil movie.id + assert_nil movie.movieid end def test_assign_id_after_rollback @@ -707,8 +707,54 @@ class TransactionTest < ActiveRecord::TestCase raise ActiveRecord::Rollback end - movie.id = nil - assert_nil movie.id + movie.movieid = nil + assert_nil movie.movieid + end + + def test_read_attribute_after_rollback + topic = Topic.new + + Topic.transaction do + topic.save! + raise ActiveRecord::Rollback + end + + assert_nil topic.read_attribute(:id) + end + + def test_read_attribute_with_custom_primary_key_after_rollback + movie = Movie.new(name: "foo") + + Movie.transaction do + movie.save! + raise ActiveRecord::Rollback + end + + assert_nil movie.read_attribute(:movieid) + end + + def test_write_attribute_after_rollback + topic = Topic.create! + + Topic.transaction do + topic.save! + raise ActiveRecord::Rollback + end + + topic.write_attribute(:id, nil) + assert_nil topic.id + end + + def test_write_attribute_with_custom_primary_key_after_rollback + movie = Movie.create!(name: "foo") + + Movie.transaction do + movie.save! + raise ActiveRecord::Rollback + end + + movie.write_attribute(:movieid, nil) + assert_nil movie.movieid end def test_rollback_of_frozen_records -- cgit v1.2.3