2011-06-06 14:17:44 -04:00
|
|
|
require "cases/helper"
|
2008-01-18 02:31:37 -05:00
|
|
|
require 'models/topic'
|
|
|
|
require 'models/reply'
|
|
|
|
require 'models/developer'
|
2014-11-14 03:55:32 -05:00
|
|
|
require 'models/computer'
|
2008-08-23 20:51:45 -04:00
|
|
|
require 'models/book'
|
2010-07-13 15:30:23 -04:00
|
|
|
require 'models/author'
|
|
|
|
require 'models/post'
|
2014-03-15 06:32:24 -04:00
|
|
|
require 'models/movie'
|
2004-11-23 20:04:44 -05:00
|
|
|
|
2008-01-21 12:20:51 -05:00
|
|
|
class TransactionTest < ActiveRecord::TestCase
|
2015-03-10 22:21:19 -04:00
|
|
|
self.use_transactional_tests = false
|
2010-07-13 15:30:23 -04:00
|
|
|
fixtures :topics, :developers, :authors, :posts
|
2005-06-10 10:58:02 -04:00
|
|
|
|
2004-11-23 20:04:44 -05:00
|
|
|
def setup
|
2014-10-27 12:28:53 -04:00
|
|
|
@first, @second = Topic.find(1, 2).sort_by(&:id)
|
2004-11-23 20:04:44 -05:00
|
|
|
end
|
|
|
|
|
2014-03-15 06:32:24 -04:00
|
|
|
def test_persisted_in_a_model_with_custom_primary_key_after_failed_save
|
|
|
|
movie = Movie.create
|
|
|
|
assert !movie.persisted?
|
|
|
|
end
|
|
|
|
|
2012-10-22 19:48:09 -04:00
|
|
|
def test_raise_after_destroy
|
2012-12-28 18:49:41 -05:00
|
|
|
assert_not @first.frozen?
|
2012-10-22 19:48:09 -04:00
|
|
|
|
|
|
|
assert_raises(RuntimeError) {
|
|
|
|
Topic.transaction do
|
|
|
|
@first.destroy
|
|
|
|
assert @first.frozen?
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
assert @first.reload
|
2012-12-28 18:49:41 -05:00
|
|
|
assert_not @first.frozen?
|
2012-10-22 19:48:09 -04:00
|
|
|
end
|
|
|
|
|
2004-11-23 20:04:44 -05:00
|
|
|
def test_successful
|
|
|
|
Topic.transaction do
|
2005-09-29 23:39:15 -04:00
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
2004-11-23 20:04:44 -05:00
|
|
|
@first.save
|
|
|
|
@second.save
|
|
|
|
end
|
|
|
|
|
|
|
|
assert Topic.find(1).approved?, "First should have been approved"
|
|
|
|
assert !Topic.find(2).approved?, "Second should have been unapproved"
|
|
|
|
end
|
|
|
|
|
2005-09-28 14:53:22 -04:00
|
|
|
def transaction_with_return
|
|
|
|
Topic.transaction do
|
2005-09-29 23:39:15 -04:00
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
2005-09-28 14:53:22 -04:00
|
|
|
@first.save
|
|
|
|
@second.save
|
|
|
|
return
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_successful_with_return
|
2012-09-20 22:14:34 -04:00
|
|
|
committed = false
|
|
|
|
|
|
|
|
Topic.connection.class_eval do
|
2005-09-28 14:53:22 -04:00
|
|
|
alias :real_commit_db_transaction :commit_db_transaction
|
2012-09-20 22:14:34 -04:00
|
|
|
define_method(:commit_db_transaction) do
|
|
|
|
committed = true
|
2005-09-29 07:16:51 -04:00
|
|
|
real_commit_db_transaction
|
2005-09-28 14:53:22 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
transaction_with_return
|
2012-09-20 22:14:34 -04:00
|
|
|
assert committed
|
2005-09-28 14:53:22 -04:00
|
|
|
|
|
|
|
assert Topic.find(1).approved?, "First should have been approved"
|
|
|
|
assert !Topic.find(2).approved?, "Second should have been unapproved"
|
|
|
|
ensure
|
2012-09-20 22:14:34 -04:00
|
|
|
Topic.connection.class_eval do
|
2010-03-18 13:23:14 -04:00
|
|
|
remove_method :commit_db_transaction
|
2005-09-28 14:53:22 -04:00
|
|
|
alias :commit_db_transaction :real_commit_db_transaction rescue nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-08-08 15:37:38 -04:00
|
|
|
def test_number_of_transactions_in_commit
|
|
|
|
num = nil
|
|
|
|
|
|
|
|
Topic.connection.class_eval do
|
|
|
|
alias :real_commit_db_transaction :commit_db_transaction
|
|
|
|
define_method(:commit_db_transaction) do
|
|
|
|
num = transaction_manager.open_transactions
|
|
|
|
real_commit_db_transaction
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@first.save!
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_equal 0, num
|
|
|
|
ensure
|
|
|
|
Topic.connection.class_eval do
|
|
|
|
remove_method :commit_db_transaction
|
|
|
|
alias :commit_db_transaction :real_commit_db_transaction rescue nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2004-11-23 20:04:44 -05:00
|
|
|
def test_successful_with_instance_method
|
|
|
|
@first.transaction do
|
2005-09-29 23:39:15 -04:00
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
2004-11-23 20:04:44 -05:00
|
|
|
@first.save
|
|
|
|
@second.save
|
|
|
|
end
|
|
|
|
|
|
|
|
assert Topic.find(1).approved?, "First should have been approved"
|
|
|
|
assert !Topic.find(2).approved?, "Second should have been unapproved"
|
|
|
|
end
|
2005-09-28 14:53:22 -04:00
|
|
|
|
2004-11-23 20:04:44 -05:00
|
|
|
def test_failing_on_exception
|
|
|
|
begin
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
|
|
|
@first.save
|
|
|
|
@second.save
|
|
|
|
raise "Bad things!"
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
# caught it
|
|
|
|
end
|
|
|
|
|
|
|
|
assert @first.approved?, "First should still be changed in the objects"
|
|
|
|
assert !@second.approved?, "Second should still be changed in the objects"
|
2007-06-22 20:54:51 -04:00
|
|
|
|
2004-11-23 20:04:44 -05:00
|
|
|
assert !Topic.find(1).approved?, "First shouldn't have been approved"
|
|
|
|
assert Topic.find(2).approved?, "Second should still be approved"
|
|
|
|
end
|
2007-06-22 20:54:51 -04:00
|
|
|
|
2008-08-23 20:51:45 -04:00
|
|
|
def test_raising_exception_in_callback_rollbacks_in_save
|
2012-08-20 18:32:50 -04:00
|
|
|
def @first.after_save_for_transaction
|
|
|
|
raise 'Make the transaction rollback'
|
2004-11-23 20:04:44 -05:00
|
|
|
end
|
2012-08-20 18:32:50 -04:00
|
|
|
|
|
|
|
@first.approved = true
|
|
|
|
e = assert_raises(RuntimeError) { @first.save }
|
|
|
|
assert_equal "Make the transaction rollback", e.message
|
|
|
|
assert !Topic.find(1).approved?
|
2004-11-23 20:04:44 -05:00
|
|
|
end
|
2007-06-22 20:54:51 -04:00
|
|
|
|
2014-05-15 15:38:05 -04:00
|
|
|
def test_rolling_back_in_a_callback_rollbacks_before_save
|
|
|
|
def @first.before_save_for_transaction
|
|
|
|
raise ActiveRecord::Rollback
|
|
|
|
end
|
|
|
|
assert !@first.approved
|
|
|
|
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@first.save!
|
|
|
|
end
|
|
|
|
assert !Topic.find(@first.id).approved?, "Should not commit the approved flag"
|
|
|
|
end
|
|
|
|
|
2013-07-08 05:31:07 -04:00
|
|
|
def test_raising_exception_in_nested_transaction_restore_state_in_save
|
|
|
|
topic = Topic.new
|
|
|
|
|
|
|
|
def topic.after_save_for_transaction
|
|
|
|
raise 'Make the transaction rollback'
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_raises(RuntimeError) do
|
|
|
|
Topic.transaction { topic.save }
|
|
|
|
end
|
|
|
|
|
|
|
|
assert topic.new_record?, "#{topic.inspect} should be new record"
|
|
|
|
end
|
|
|
|
|
2015-07-19 22:00:36 -04:00
|
|
|
def test_transaction_state_is_cleared_when_record_is_persisted
|
|
|
|
author = Author.create! name: 'foo'
|
|
|
|
author.name = nil
|
|
|
|
assert_not author.save
|
|
|
|
assert_not author.new_record?
|
|
|
|
end
|
|
|
|
|
2013-01-02 11:46:58 -05:00
|
|
|
def test_update_should_rollback_on_failure
|
2010-07-13 15:30:23 -04:00
|
|
|
author = Author.find(1)
|
|
|
|
posts_count = author.posts.size
|
|
|
|
assert posts_count > 0
|
2013-01-02 11:46:58 -05:00
|
|
|
status = author.update(name: nil, post_ids: [])
|
2010-07-13 15:30:23 -04:00
|
|
|
assert !status
|
2015-07-16 11:08:49 -04:00
|
|
|
assert_equal posts_count, author.posts.reload.size
|
2010-07-13 15:30:23 -04:00
|
|
|
end
|
|
|
|
|
2013-01-02 11:46:58 -05:00
|
|
|
def test_update_should_rollback_on_failure!
|
2010-07-13 15:30:23 -04:00
|
|
|
author = Author.find(1)
|
|
|
|
posts_count = author.posts.size
|
|
|
|
assert posts_count > 0
|
|
|
|
assert_raise(ActiveRecord::RecordInvalid) do
|
2013-01-02 11:46:58 -05:00
|
|
|
author.update!(name: nil, post_ids: [])
|
2010-07-13 15:30:23 -04:00
|
|
|
end
|
2015-07-16 11:08:49 -04:00
|
|
|
assert_equal posts_count, author.posts.reload.size
|
2010-07-13 15:30:23 -04:00
|
|
|
end
|
|
|
|
|
2014-12-15 01:10:15 -05:00
|
|
|
def test_cancellation_from_returning_false_in_before_filter
|
|
|
|
def @first.before_save_for_transaction
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_deprecated do
|
|
|
|
@first.save
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2008-08-23 20:51:45 -04:00
|
|
|
def test_cancellation_from_before_destroy_rollbacks_in_destroy
|
2012-08-20 19:12:09 -04:00
|
|
|
add_cancelling_before_destroy_with_db_side_effect_to_topic @first
|
|
|
|
nbooks_before_destroy = Book.count
|
|
|
|
status = @first.destroy
|
|
|
|
assert !status
|
|
|
|
@first.reload
|
|
|
|
assert_equal nbooks_before_destroy, Book.count
|
2008-08-23 20:51:45 -04:00
|
|
|
end
|
|
|
|
|
2012-08-20 19:12:09 -04:00
|
|
|
%w(validation save).each do |filter|
|
|
|
|
define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}") do
|
|
|
|
send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first)
|
|
|
|
nbooks_before_save = Book.count
|
|
|
|
original_author_name = @first.author_name
|
|
|
|
@first.author_name += '_this_should_not_end_up_in_the_db'
|
|
|
|
status = @first.save
|
|
|
|
assert !status
|
|
|
|
assert_equal original_author_name, @first.reload.author_name
|
|
|
|
assert_equal nbooks_before_save, Book.count
|
2008-08-23 20:51:45 -04:00
|
|
|
end
|
|
|
|
|
2012-08-20 19:12:09 -04:00
|
|
|
define_method("test_cancellation_from_before_filters_rollbacks_in_#{filter}!") do
|
|
|
|
send("add_cancelling_before_#{filter}_with_db_side_effect_to_topic", @first)
|
|
|
|
nbooks_before_save = Book.count
|
|
|
|
original_author_name = @first.author_name
|
|
|
|
@first.author_name += '_this_should_not_end_up_in_the_db'
|
|
|
|
|
2008-08-23 20:51:45 -04:00
|
|
|
begin
|
|
|
|
@first.save!
|
2012-08-20 19:12:09 -04:00
|
|
|
rescue ActiveRecord::RecordInvalid, ActiveRecord::RecordNotSaved
|
2008-08-23 20:51:45 -04:00
|
|
|
end
|
2012-08-20 19:12:09 -04:00
|
|
|
|
|
|
|
assert_equal original_author_name, @first.reload.author_name
|
|
|
|
assert_equal nbooks_before_save, Book.count
|
2008-08-23 20:51:45 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2007-01-02 00:36:30 -05:00
|
|
|
def test_callback_rollback_in_create
|
2012-08-20 18:32:50 -04:00
|
|
|
topic = Class.new(Topic) {
|
|
|
|
def after_create_for_transaction
|
|
|
|
raise 'Make the transaction rollback'
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
|
|
|
new_topic = topic.new(:title => "A new topic",
|
|
|
|
:author_name => "Ben",
|
|
|
|
:author_email_address => "ben@example.com",
|
|
|
|
:written_on => "2003-07-16t15:28:11.2233+01:00",
|
|
|
|
:last_read => "2004-04-15",
|
|
|
|
:bonus_time => "2005-01-30t15:28:00.00+01:00",
|
|
|
|
:content => "Have a nice day",
|
|
|
|
:approved => false)
|
|
|
|
|
2010-11-07 09:05:18 -05:00
|
|
|
new_record_snapshot = !new_topic.persisted?
|
2007-01-12 00:10:06 -05:00
|
|
|
id_present = new_topic.has_attribute?(Topic.primary_key)
|
2007-01-02 00:36:30 -05:00
|
|
|
id_snapshot = new_topic.id
|
2007-01-12 00:10:06 -05:00
|
|
|
|
2007-01-02 00:36:30 -05:00
|
|
|
# Make sure the second save gets the after_create callback called.
|
|
|
|
2.times do
|
2012-08-20 18:32:50 -04:00
|
|
|
new_topic.approved = true
|
|
|
|
e = assert_raises(RuntimeError) { new_topic.save }
|
|
|
|
assert_equal "Make the transaction rollback", e.message
|
|
|
|
assert_equal new_record_snapshot, !new_topic.persisted?, "The topic should have its old persisted value"
|
|
|
|
assert_equal id_snapshot, new_topic.id, "The topic should have its old id"
|
|
|
|
assert_equal id_present, new_topic.has_attribute?(Topic.primary_key)
|
2007-01-02 00:36:30 -05:00
|
|
|
end
|
|
|
|
end
|
2004-11-23 20:04:44 -05:00
|
|
|
|
2012-04-29 22:38:32 -04:00
|
|
|
def test_callback_rollback_in_create_with_record_invalid_exception
|
2012-08-20 18:32:50 -04:00
|
|
|
topic = Class.new(Topic) {
|
|
|
|
def after_create_for_transaction
|
|
|
|
raise ActiveRecord::RecordInvalid.new(Author.new)
|
|
|
|
end
|
|
|
|
}
|
2012-04-29 22:38:32 -04:00
|
|
|
|
2012-08-20 18:32:50 -04:00
|
|
|
new_topic = topic.create(:title => "A new topic")
|
|
|
|
assert !new_topic.persisted?, "The topic should not be persisted"
|
|
|
|
assert_nil new_topic.id, "The topic should not have an ID"
|
2012-04-29 22:38:32 -04:00
|
|
|
end
|
|
|
|
|
2004-12-21 19:48:24 -05:00
|
|
|
def test_nested_explicit_transactions
|
2004-11-23 20:04:44 -05:00
|
|
|
Topic.transaction do
|
|
|
|
Topic.transaction do
|
2005-09-29 23:39:15 -04:00
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
2004-11-23 20:04:44 -05:00
|
|
|
@first.save
|
|
|
|
@second.save
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert Topic.find(1).approved?, "First should have been approved"
|
|
|
|
assert !Topic.find(2).approved?, "Second should have been unapproved"
|
|
|
|
end
|
2005-09-29 23:39:15 -04:00
|
|
|
|
2007-02-21 17:13:39 -05:00
|
|
|
def test_manually_rolling_back_a_transaction
|
2007-06-22 20:54:51 -04:00
|
|
|
Topic.transaction do
|
2007-02-21 17:13:39 -05:00
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
|
|
|
@first.save
|
|
|
|
@second.save
|
2007-06-22 20:54:51 -04:00
|
|
|
|
2007-05-17 21:02:08 -04:00
|
|
|
raise ActiveRecord::Rollback
|
2007-02-21 17:13:39 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
assert @first.approved?, "First should still be changed in the objects"
|
|
|
|
assert !@second.approved?, "Second should still be changed in the objects"
|
2007-06-22 20:54:51 -04:00
|
|
|
|
2007-02-21 17:13:39 -05:00
|
|
|
assert !Topic.find(1).approved?, "First shouldn't have been approved"
|
|
|
|
assert Topic.find(2).approved?, "Second should still be approved"
|
|
|
|
end
|
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
def test_invalid_keys_for_transaction
|
2009-03-08 16:11:58 -04:00
|
|
|
assert_raise ArgumentError do
|
2008-10-09 10:24:15 -04:00
|
|
|
Topic.transaction :nested => true do
|
2008-08-31 05:09:16 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_force_savepoint_in_nested_transaction
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
|
|
|
@first.save!
|
|
|
|
@second.save!
|
|
|
|
|
|
|
|
begin
|
2009-01-10 16:36:09 -05:00
|
|
|
Topic.transaction :requires_new => true do
|
2008-08-31 05:09:16 -04:00
|
|
|
@first.happy = false
|
|
|
|
@first.save!
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert @first.reload.approved?
|
|
|
|
assert !@second.reload.approved?
|
2008-10-09 09:41:56 -04:00
|
|
|
end if Topic.connection.supports_savepoints?
|
2008-08-31 05:09:16 -04:00
|
|
|
|
2010-10-15 00:27:40 -04:00
|
|
|
def test_force_savepoint_on_instance
|
|
|
|
@first.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
|
|
|
@first.save!
|
|
|
|
@second.save!
|
|
|
|
|
|
|
|
begin
|
|
|
|
@second.transaction :requires_new => true do
|
|
|
|
@first.happy = false
|
|
|
|
@first.save!
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert @first.reload.approved?
|
|
|
|
assert !@second.reload.approved?
|
|
|
|
end if Topic.connection.supports_savepoints?
|
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
def test_no_savepoint_in_nested_transaction_without_force
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
|
|
|
@first.save!
|
|
|
|
@second.save!
|
|
|
|
|
|
|
|
begin
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = false
|
|
|
|
@first.save!
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert !@first.reload.approved?
|
|
|
|
assert !@second.reload.approved?
|
2008-10-09 09:41:56 -04:00
|
|
|
end if Topic.connection.supports_savepoints?
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
def test_many_savepoints
|
|
|
|
Topic.transaction do
|
|
|
|
@first.content = "One"
|
|
|
|
@first.save!
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
begin
|
2009-01-10 16:36:09 -05:00
|
|
|
Topic.transaction :requires_new => true do
|
2008-08-31 05:09:16 -04:00
|
|
|
@first.content = "Two"
|
|
|
|
@first.save!
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
begin
|
2009-01-10 16:36:09 -05:00
|
|
|
Topic.transaction :requires_new => true do
|
2008-08-31 05:09:16 -04:00
|
|
|
@first.content = "Three"
|
|
|
|
@first.save!
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
begin
|
2009-01-10 16:36:09 -05:00
|
|
|
Topic.transaction :requires_new => true do
|
2008-08-31 05:09:16 -04:00
|
|
|
@first.content = "Four"
|
|
|
|
@first.save!
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
end
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
@three = @first.reload.content
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
end
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
@two = @first.reload.content
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
end
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
@one = @first.reload.content
|
|
|
|
end
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
assert_equal "One", @one
|
|
|
|
assert_equal "Two", @two
|
|
|
|
assert_equal "Three", @three
|
2008-10-09 09:41:56 -04:00
|
|
|
end if Topic.connection.supports_savepoints?
|
2008-08-31 05:09:16 -04:00
|
|
|
|
2013-09-30 04:58:25 -04:00
|
|
|
def test_using_named_savepoints
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@first.save!
|
|
|
|
Topic.connection.create_savepoint("first")
|
|
|
|
|
|
|
|
@first.approved = false
|
|
|
|
@first.save!
|
|
|
|
Topic.connection.rollback_to_savepoint("first")
|
|
|
|
assert @first.reload.approved?
|
|
|
|
|
|
|
|
@first.approved = false
|
|
|
|
@first.save!
|
|
|
|
Topic.connection.release_savepoint("first")
|
|
|
|
assert_not @first.reload.approved?
|
|
|
|
end
|
|
|
|
end if Topic.connection.supports_savepoints?
|
|
|
|
|
|
|
|
def test_releasing_named_savepoints
|
|
|
|
Topic.transaction do
|
|
|
|
Topic.connection.create_savepoint("another")
|
|
|
|
Topic.connection.release_savepoint("another")
|
|
|
|
|
|
|
|
# The savepoint is now gone and we can't remove it again.
|
|
|
|
assert_raises(ActiveRecord::StatementInvalid) do
|
|
|
|
Topic.connection.release_savepoint("another")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2014-07-28 13:25:19 -04:00
|
|
|
def test_savepoints_name
|
|
|
|
Topic.transaction do
|
|
|
|
assert_nil Topic.connection.current_savepoint_name
|
|
|
|
assert_nil Topic.connection.current_transaction.savepoint_name
|
|
|
|
|
|
|
|
Topic.transaction(requires_new: true) do
|
|
|
|
assert_equal "active_record_1", Topic.connection.current_savepoint_name
|
|
|
|
assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name
|
|
|
|
|
|
|
|
Topic.transaction(requires_new: true) do
|
|
|
|
assert_equal "active_record_2", Topic.connection.current_savepoint_name
|
|
|
|
assert_equal "active_record_2", Topic.connection.current_transaction.savepoint_name
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_equal "active_record_1", Topic.connection.current_savepoint_name
|
|
|
|
assert_equal "active_record_1", Topic.connection.current_transaction.savepoint_name
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-02-03 21:25:37 -05:00
|
|
|
def test_rollback_when_commit_raises
|
2015-09-09 15:00:40 -04:00
|
|
|
assert_called(Topic.connection, :begin_db_transaction) do
|
|
|
|
Topic.connection.stub(:commit_db_transaction, ->{ raise('OH NOES') }) do
|
|
|
|
assert_called(Topic.connection, :rollback_db_transaction) do
|
2007-06-22 20:54:51 -04:00
|
|
|
|
2015-09-09 15:00:40 -04:00
|
|
|
e = assert_raise RuntimeError do
|
|
|
|
Topic.transaction do
|
|
|
|
# do nothing
|
|
|
|
end
|
|
|
|
end
|
|
|
|
assert_equal 'OH NOES', e.message
|
|
|
|
end
|
2007-06-22 20:54:51 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2012-05-21 17:57:04 -04:00
|
|
|
def test_rollback_when_saving_a_frozen_record
|
|
|
|
topic = Topic.new(:title => 'test')
|
|
|
|
topic.freeze
|
|
|
|
e = assert_raise(RuntimeError) { topic.save }
|
2013-10-07 04:20:05 -04:00
|
|
|
assert_match(/frozen/i, e.message) # Not good enough, but we can't do much
|
|
|
|
# about it since there is no specific error
|
|
|
|
# for frozen objects.
|
2012-05-21 17:57:04 -04:00
|
|
|
assert !topic.persisted?, 'not persisted'
|
|
|
|
assert_nil topic.id
|
|
|
|
assert topic.frozen?, 'not frozen'
|
|
|
|
end
|
|
|
|
|
2014-11-28 21:55:11 -05:00
|
|
|
def test_rollback_when_thread_killed
|
2015-01-05 04:33:05 -05:00
|
|
|
return if in_memory_db?
|
|
|
|
|
2014-11-28 21:55:11 -05:00
|
|
|
queue = Queue.new
|
|
|
|
thread = Thread.new do
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@second.approved = false
|
|
|
|
@first.save
|
2014-01-09 18:21:41 -05:00
|
|
|
|
2014-11-28 21:55:11 -05:00
|
|
|
queue.push nil
|
|
|
|
sleep
|
2014-01-09 18:21:41 -05:00
|
|
|
|
2014-11-28 21:55:11 -05:00
|
|
|
@second.save
|
2014-01-09 18:21:41 -05:00
|
|
|
end
|
2014-11-28 21:55:11 -05:00
|
|
|
end
|
2014-01-09 18:21:41 -05:00
|
|
|
|
2014-11-28 21:55:11 -05:00
|
|
|
queue.pop
|
|
|
|
thread.kill
|
|
|
|
thread.join
|
2014-01-09 18:21:41 -05:00
|
|
|
|
2014-11-28 21:55:11 -05:00
|
|
|
assert @first.approved?, "First should still be changed in the objects"
|
|
|
|
assert !@second.approved?, "Second should still be changed in the objects"
|
2014-01-09 18:21:41 -05:00
|
|
|
|
2014-11-28 21:55:11 -05:00
|
|
|
assert !Topic.find(1).approved?, "First shouldn't have been approved"
|
|
|
|
assert Topic.find(2).approved?, "Second should still be approved"
|
2014-01-09 18:21:41 -05:00
|
|
|
end
|
|
|
|
|
2010-06-08 16:59:06 -04:00
|
|
|
def test_restore_active_record_state_for_all_records_in_a_transaction
|
Restore ActiveRecord states after a rollback for models w/o callbacks
This fixes a regression (#13744) that was caused by 67d8bb9.
In 67d8bb9, we introduced lazy rollback for records, such that the
record's internal states and attributes are not restored immediately
after a transaction rollback, but deferred until they are first
accessed.
This optimization is only performed when the model does not have any
transactional callbacks (e.g. `after_commit` and `after_create`).
Unfortunately, the models used to test the affected codepaths all
comes with some sort of transactional callbacks. Therefore this
codepath remains largely untested until now and as a result there are
a few issues in the implementation that remains hidden until now.
First, the `sync_with_transaction_state` (or more accurately,
`update_attributes_from_transaction_state`) would perform the
synchronization prematurely before a transaction is finalized (i.e.
comitted or rolled back). As a result, when the actuall rollback
happens, the record will incorrectly assumes that its internal states
match the transaction state, and neglect to perform the restore.
Second, `update_attributes_from_transaction_state` calls `committed!`
in some cases. This in turns checks for the `destroyed?` state which
also requires synchronization with the transaction stae, which causes
an infnite recurrsion.
This fix works by deferring the synchronization until the transaction
has been finalized (addressing the first point), and also unrolled
the `committed!` and `rolledback!` logic in-place (addressing the
second point).
It should be noted that the primary purpose of the `committed!` and
`rolledback!` methods are to trigger the relevant transactional
callbacks. Since this code path is only entered when there are no
transactional callbacks on the model, this shouldn't be necessary. By
unrolling the method calls, the intention here (to restore the states
when necessary) becomes more clear.
2014-01-18 07:36:45 -05:00
|
|
|
topic_without_callbacks = Class.new(ActiveRecord::Base) do
|
|
|
|
self.table_name = 'topics'
|
|
|
|
end
|
|
|
|
|
2010-06-08 16:59:06 -04:00
|
|
|
topic_1 = Topic.new(:title => 'test_1')
|
|
|
|
topic_2 = Topic.new(:title => 'test_2')
|
Restore ActiveRecord states after a rollback for models w/o callbacks
This fixes a regression (#13744) that was caused by 67d8bb9.
In 67d8bb9, we introduced lazy rollback for records, such that the
record's internal states and attributes are not restored immediately
after a transaction rollback, but deferred until they are first
accessed.
This optimization is only performed when the model does not have any
transactional callbacks (e.g. `after_commit` and `after_create`).
Unfortunately, the models used to test the affected codepaths all
comes with some sort of transactional callbacks. Therefore this
codepath remains largely untested until now and as a result there are
a few issues in the implementation that remains hidden until now.
First, the `sync_with_transaction_state` (or more accurately,
`update_attributes_from_transaction_state`) would perform the
synchronization prematurely before a transaction is finalized (i.e.
comitted or rolled back). As a result, when the actuall rollback
happens, the record will incorrectly assumes that its internal states
match the transaction state, and neglect to perform the restore.
Second, `update_attributes_from_transaction_state` calls `committed!`
in some cases. This in turns checks for the `destroyed?` state which
also requires synchronization with the transaction stae, which causes
an infnite recurrsion.
This fix works by deferring the synchronization until the transaction
has been finalized (addressing the first point), and also unrolled
the `committed!` and `rolledback!` logic in-place (addressing the
second point).
It should be noted that the primary purpose of the `committed!` and
`rolledback!` methods are to trigger the relevant transactional
callbacks. Since this code path is only entered when there are no
transactional callbacks on the model, this shouldn't be necessary. By
unrolling the method calls, the intention here (to restore the states
when necessary) becomes more clear.
2014-01-18 07:36:45 -05:00
|
|
|
topic_3 = topic_without_callbacks.new(:title => 'test_3')
|
|
|
|
|
2010-06-08 16:59:06 -04:00
|
|
|
Topic.transaction do
|
|
|
|
assert topic_1.save
|
|
|
|
assert topic_2.save
|
Restore ActiveRecord states after a rollback for models w/o callbacks
This fixes a regression (#13744) that was caused by 67d8bb9.
In 67d8bb9, we introduced lazy rollback for records, such that the
record's internal states and attributes are not restored immediately
after a transaction rollback, but deferred until they are first
accessed.
This optimization is only performed when the model does not have any
transactional callbacks (e.g. `after_commit` and `after_create`).
Unfortunately, the models used to test the affected codepaths all
comes with some sort of transactional callbacks. Therefore this
codepath remains largely untested until now and as a result there are
a few issues in the implementation that remains hidden until now.
First, the `sync_with_transaction_state` (or more accurately,
`update_attributes_from_transaction_state`) would perform the
synchronization prematurely before a transaction is finalized (i.e.
comitted or rolled back). As a result, when the actuall rollback
happens, the record will incorrectly assumes that its internal states
match the transaction state, and neglect to perform the restore.
Second, `update_attributes_from_transaction_state` calls `committed!`
in some cases. This in turns checks for the `destroyed?` state which
also requires synchronization with the transaction stae, which causes
an infnite recurrsion.
This fix works by deferring the synchronization until the transaction
has been finalized (addressing the first point), and also unrolled
the `committed!` and `rolledback!` logic in-place (addressing the
second point).
It should be noted that the primary purpose of the `committed!` and
`rolledback!` methods are to trigger the relevant transactional
callbacks. Since this code path is only entered when there are no
transactional callbacks on the model, this shouldn't be necessary. By
unrolling the method calls, the intention here (to restore the states
when necessary) becomes more clear.
2014-01-18 07:36:45 -05:00
|
|
|
assert topic_3.save
|
2010-06-08 16:59:06 -04:00
|
|
|
@first.save
|
|
|
|
@second.destroy
|
2010-12-13 14:39:31 -05:00
|
|
|
assert topic_1.persisted?, 'persisted'
|
2010-06-08 16:59:06 -04:00
|
|
|
assert_not_nil topic_1.id
|
2010-12-13 14:39:31 -05:00
|
|
|
assert topic_2.persisted?, 'persisted'
|
2010-06-08 16:59:06 -04:00
|
|
|
assert_not_nil topic_2.id
|
Restore ActiveRecord states after a rollback for models w/o callbacks
This fixes a regression (#13744) that was caused by 67d8bb9.
In 67d8bb9, we introduced lazy rollback for records, such that the
record's internal states and attributes are not restored immediately
after a transaction rollback, but deferred until they are first
accessed.
This optimization is only performed when the model does not have any
transactional callbacks (e.g. `after_commit` and `after_create`).
Unfortunately, the models used to test the affected codepaths all
comes with some sort of transactional callbacks. Therefore this
codepath remains largely untested until now and as a result there are
a few issues in the implementation that remains hidden until now.
First, the `sync_with_transaction_state` (or more accurately,
`update_attributes_from_transaction_state`) would perform the
synchronization prematurely before a transaction is finalized (i.e.
comitted or rolled back). As a result, when the actuall rollback
happens, the record will incorrectly assumes that its internal states
match the transaction state, and neglect to perform the restore.
Second, `update_attributes_from_transaction_state` calls `committed!`
in some cases. This in turns checks for the `destroyed?` state which
also requires synchronization with the transaction stae, which causes
an infnite recurrsion.
This fix works by deferring the synchronization until the transaction
has been finalized (addressing the first point), and also unrolled
the `committed!` and `rolledback!` logic in-place (addressing the
second point).
It should be noted that the primary purpose of the `committed!` and
`rolledback!` methods are to trigger the relevant transactional
callbacks. Since this code path is only entered when there are no
transactional callbacks on the model, this shouldn't be necessary. By
unrolling the method calls, the intention here (to restore the states
when necessary) becomes more clear.
2014-01-18 07:36:45 -05:00
|
|
|
assert topic_3.persisted?, 'persisted'
|
|
|
|
assert_not_nil topic_3.id
|
2010-12-13 14:39:31 -05:00
|
|
|
assert @first.persisted?, 'persisted'
|
2010-06-08 16:59:06 -04:00
|
|
|
assert_not_nil @first.id
|
2010-12-13 14:39:31 -05:00
|
|
|
assert @second.destroyed?, 'destroyed'
|
2010-06-08 16:59:06 -04:00
|
|
|
raise ActiveRecord::Rollback
|
|
|
|
end
|
|
|
|
|
2010-12-13 14:39:31 -05:00
|
|
|
assert !topic_1.persisted?, 'not persisted'
|
2010-06-08 16:59:06 -04:00
|
|
|
assert_nil topic_1.id
|
2010-12-13 14:39:31 -05:00
|
|
|
assert !topic_2.persisted?, 'not persisted'
|
2010-06-08 16:59:06 -04:00
|
|
|
assert_nil topic_2.id
|
Restore ActiveRecord states after a rollback for models w/o callbacks
This fixes a regression (#13744) that was caused by 67d8bb9.
In 67d8bb9, we introduced lazy rollback for records, such that the
record's internal states and attributes are not restored immediately
after a transaction rollback, but deferred until they are first
accessed.
This optimization is only performed when the model does not have any
transactional callbacks (e.g. `after_commit` and `after_create`).
Unfortunately, the models used to test the affected codepaths all
comes with some sort of transactional callbacks. Therefore this
codepath remains largely untested until now and as a result there are
a few issues in the implementation that remains hidden until now.
First, the `sync_with_transaction_state` (or more accurately,
`update_attributes_from_transaction_state`) would perform the
synchronization prematurely before a transaction is finalized (i.e.
comitted or rolled back). As a result, when the actuall rollback
happens, the record will incorrectly assumes that its internal states
match the transaction state, and neglect to perform the restore.
Second, `update_attributes_from_transaction_state` calls `committed!`
in some cases. This in turns checks for the `destroyed?` state which
also requires synchronization with the transaction stae, which causes
an infnite recurrsion.
This fix works by deferring the synchronization until the transaction
has been finalized (addressing the first point), and also unrolled
the `committed!` and `rolledback!` logic in-place (addressing the
second point).
It should be noted that the primary purpose of the `committed!` and
`rolledback!` methods are to trigger the relevant transactional
callbacks. Since this code path is only entered when there are no
transactional callbacks on the model, this shouldn't be necessary. By
unrolling the method calls, the intention here (to restore the states
when necessary) becomes more clear.
2014-01-18 07:36:45 -05:00
|
|
|
assert !topic_3.persisted?, 'not persisted'
|
|
|
|
assert_nil topic_3.id
|
2010-12-13 14:39:31 -05:00
|
|
|
assert @first.persisted?, 'persisted'
|
2010-06-08 16:59:06 -04:00
|
|
|
assert_not_nil @first.id
|
2010-12-13 14:39:31 -05:00
|
|
|
assert !@second.destroyed?, 'not destroyed'
|
2010-06-08 16:59:06 -04:00
|
|
|
end
|
|
|
|
|
2014-12-25 15:33:47 -05:00
|
|
|
def test_restore_frozen_state_after_double_destroy
|
|
|
|
topic = Topic.create
|
|
|
|
reply = topic.replies.create
|
|
|
|
|
|
|
|
Topic.transaction do
|
|
|
|
topic.destroy # calls #destroy on reply (since dependent: destroy)
|
|
|
|
reply.destroy
|
|
|
|
|
|
|
|
raise ActiveRecord::Rollback
|
|
|
|
end
|
|
|
|
|
2014-12-26 15:11:18 -05:00
|
|
|
assert_not reply.frozen?
|
|
|
|
assert_not topic.frozen?
|
2014-12-25 15:33:47 -05:00
|
|
|
end
|
|
|
|
|
2014-12-25 15:46:49 -05:00
|
|
|
def test_rollback_of_frozen_records
|
|
|
|
topic = Topic.create.freeze
|
|
|
|
Topic.transaction do
|
|
|
|
topic.destroy
|
|
|
|
raise ActiveRecord::Rollback
|
|
|
|
end
|
|
|
|
assert topic.frozen?, 'frozen'
|
|
|
|
end
|
|
|
|
|
2015-03-04 07:24:03 -05:00
|
|
|
def test_rollback_for_freshly_persisted_records
|
|
|
|
topic = Topic.create
|
|
|
|
Topic.transaction do
|
|
|
|
topic.destroy
|
|
|
|
raise ActiveRecord::Rollback
|
|
|
|
end
|
|
|
|
assert topic.persisted?, 'persisted'
|
|
|
|
end
|
|
|
|
|
2009-03-14 08:58:42 -04:00
|
|
|
def test_sqlite_add_column_in_transaction
|
2010-10-19 13:18:54 -04:00
|
|
|
return true unless current_adapter?(:SQLite3Adapter)
|
2008-05-20 06:10:38 -04:00
|
|
|
|
|
|
|
# Test first if column creation/deletion works correctly when no
|
|
|
|
# transaction is in place.
|
|
|
|
#
|
|
|
|
# We go back to the connection for the column queries because
|
|
|
|
# Topic.columns is cached and won't report changes to the DB
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-05-20 06:10:38 -04:00
|
|
|
assert_nothing_raised do
|
|
|
|
Topic.reset_column_information
|
|
|
|
Topic.connection.add_column('topics', 'stuff', :string)
|
|
|
|
assert Topic.column_names.include?('stuff')
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-05-20 06:10:38 -04:00
|
|
|
Topic.reset_column_information
|
|
|
|
Topic.connection.remove_column('topics', 'stuff')
|
|
|
|
assert !Topic.column_names.include?('stuff')
|
|
|
|
end
|
|
|
|
|
2009-03-14 08:58:42 -04:00
|
|
|
if Topic.connection.supports_ddl_transactions?
|
|
|
|
assert_nothing_raised do
|
|
|
|
Topic.transaction { Topic.connection.add_column('topics', 'stuff', :string) }
|
|
|
|
end
|
|
|
|
else
|
|
|
|
Topic.transaction do
|
|
|
|
assert_raise(ActiveRecord::StatementInvalid) { Topic.connection.add_column('topics', 'stuff', :string) }
|
|
|
|
raise ActiveRecord::Rollback
|
|
|
|
end
|
2008-05-20 06:10:38 -04:00
|
|
|
end
|
2013-11-12 15:11:00 -05:00
|
|
|
ensure
|
|
|
|
begin
|
|
|
|
Topic.connection.remove_column('topics', 'stuff')
|
|
|
|
rescue
|
|
|
|
ensure
|
|
|
|
Topic.reset_column_information
|
|
|
|
end
|
2008-05-20 06:10:38 -04:00
|
|
|
end
|
|
|
|
|
2013-01-20 12:40:36 -05:00
|
|
|
def test_transactions_state_from_rollback
|
|
|
|
connection = Topic.connection
|
2014-07-28 14:26:27 -04:00
|
|
|
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
|
2013-01-20 12:40:36 -05:00
|
|
|
|
|
|
|
assert transaction.open?
|
2013-01-20 13:19:05 -05:00
|
|
|
assert !transaction.state.rolledback?
|
|
|
|
assert !transaction.state.committed?
|
|
|
|
|
2014-07-31 16:44:55 -04:00
|
|
|
transaction.rollback
|
2013-01-24 23:42:39 -05:00
|
|
|
|
2013-01-20 13:19:05 -05:00
|
|
|
assert transaction.state.rolledback?
|
|
|
|
assert !transaction.state.committed?
|
2013-01-20 12:40:36 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def test_transactions_state_from_commit
|
|
|
|
connection = Topic.connection
|
2014-07-28 14:26:27 -04:00
|
|
|
transaction = ActiveRecord::ConnectionAdapters::TransactionManager.new(connection).begin_transaction
|
2013-01-20 12:40:36 -05:00
|
|
|
|
|
|
|
assert transaction.open?
|
2013-01-20 13:19:05 -05:00
|
|
|
assert !transaction.state.rolledback?
|
|
|
|
assert !transaction.state.committed?
|
|
|
|
|
2014-07-31 16:44:55 -04:00
|
|
|
transaction.commit
|
2013-01-24 23:42:39 -05:00
|
|
|
|
2013-01-20 13:19:05 -05:00
|
|
|
assert !transaction.state.rolledback?
|
|
|
|
assert transaction.state.committed?
|
2013-01-20 12:40:36 -05:00
|
|
|
end
|
|
|
|
|
2015-01-05 14:42:39 -05:00
|
|
|
def test_transaction_rollback_with_primarykeyless_tables
|
|
|
|
connection = ActiveRecord::Base.connection
|
|
|
|
connection.create_table(:transaction_without_primary_keys, force: true, id: false) do |t|
|
|
|
|
t.integer :thing_id
|
|
|
|
end
|
2015-01-05 16:20:23 -05:00
|
|
|
|
2015-01-05 14:42:39 -05:00
|
|
|
klass = Class.new(ActiveRecord::Base) do
|
|
|
|
self.table_name = 'transaction_without_primary_keys'
|
|
|
|
after_commit { } # necessary to trigger the has_transactional_callbacks branch
|
|
|
|
end
|
|
|
|
|
|
|
|
assert_no_difference(-> { klass.count }) do
|
|
|
|
ActiveRecord::Base.transaction do
|
|
|
|
klass.create!
|
|
|
|
raise ActiveRecord::Rollback
|
|
|
|
end
|
|
|
|
end
|
|
|
|
ensure
|
2015-02-18 02:54:40 -05:00
|
|
|
connection.drop_table 'transaction_without_primary_keys', if_exists: true
|
2015-01-05 14:42:39 -05:00
|
|
|
end
|
|
|
|
|
2006-06-19 18:48:51 -04:00
|
|
|
private
|
2008-08-23 20:51:45 -04:00
|
|
|
|
2012-08-20 19:12:59 -04:00
|
|
|
%w(validation save destroy).each do |filter|
|
|
|
|
define_method("add_cancelling_before_#{filter}_with_db_side_effect_to_topic") do |topic|
|
|
|
|
meta = class << topic; self; end
|
|
|
|
meta.send("define_method", "before_#{filter}_for_transaction") do
|
|
|
|
Book.create
|
2014-12-15 01:10:15 -05:00
|
|
|
throw(:abort)
|
2008-08-23 20:51:45 -04:00
|
|
|
end
|
|
|
|
end
|
2012-08-20 19:12:59 -04:00
|
|
|
end
|
2006-06-19 18:48:51 -04:00
|
|
|
end
|
2008-08-31 05:09:16 -04:00
|
|
|
|
|
|
|
class TransactionsWithTransactionalFixturesTest < ActiveRecord::TestCase
|
2015-03-10 22:21:19 -04:00
|
|
|
self.use_transactional_tests = true
|
2008-08-31 05:09:16 -04:00
|
|
|
fixtures :topics
|
|
|
|
|
|
|
|
def test_automatic_savepoint_in_outer_transaction
|
|
|
|
@first = Topic.find(1)
|
2009-06-02 15:42:22 -04:00
|
|
|
|
2008-08-31 05:09:16 -04:00
|
|
|
begin
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@first.save!
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
assert !@first.reload.approved?
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def test_no_automatic_savepoint_for_inner_transaction
|
|
|
|
@first = Topic.find(1)
|
|
|
|
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = true
|
|
|
|
@first.save!
|
|
|
|
|
|
|
|
begin
|
|
|
|
Topic.transaction do
|
|
|
|
@first.approved = false
|
|
|
|
@first.save!
|
|
|
|
raise
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert !@first.reload.approved?
|
|
|
|
end
|
2008-10-09 09:41:56 -04:00
|
|
|
end if Topic.connection.supports_savepoints?
|
2006-06-19 18:48:51 -04:00
|
|
|
|
|
|
|
if current_adapter?(:PostgreSQLAdapter)
|
|
|
|
class ConcurrentTransactionTest < TransactionTest
|
|
|
|
# This will cause transactions to overlap and fail unless they are performed on
|
|
|
|
# separate database connections.
|
2013-11-08 10:57:51 -05:00
|
|
|
unless in_memory_db?
|
|
|
|
def test_transaction_per_thread
|
|
|
|
threads = 3.times.map do
|
|
|
|
Thread.new do
|
|
|
|
Topic.transaction do
|
|
|
|
topic = Topic.find(1)
|
|
|
|
topic.approved = !topic.approved?
|
|
|
|
assert topic.save!
|
|
|
|
topic.approved = !topic.approved?
|
|
|
|
assert topic.save!
|
|
|
|
end
|
|
|
|
Topic.connection.close
|
2005-09-29 23:39:15 -04:00
|
|
|
end
|
|
|
|
end
|
2013-07-08 19:36:46 -04:00
|
|
|
|
2014-10-27 12:28:53 -04:00
|
|
|
threads.each(&:join)
|
2013-11-08 10:57:51 -05:00
|
|
|
end
|
2005-09-29 23:39:15 -04:00
|
|
|
end
|
|
|
|
|
2006-06-19 18:48:51 -04:00
|
|
|
# Test for dirty reads among simultaneous transactions.
|
|
|
|
def test_transaction_isolation__read_committed
|
|
|
|
# Should be invariant.
|
|
|
|
original_salary = Developer.find(1).salary
|
|
|
|
temporary_salary = 200000
|
|
|
|
|
|
|
|
assert_nothing_raised do
|
|
|
|
threads = (1..3).map do
|
|
|
|
Thread.new do
|
|
|
|
Developer.transaction do
|
|
|
|
# Expect original salary.
|
|
|
|
dev = Developer.find(1)
|
|
|
|
assert_equal original_salary, dev.salary
|
|
|
|
|
|
|
|
dev.salary = temporary_salary
|
|
|
|
dev.save!
|
|
|
|
|
|
|
|
# Expect temporary salary.
|
|
|
|
dev = Developer.find(1)
|
|
|
|
assert_equal temporary_salary, dev.salary
|
|
|
|
|
|
|
|
dev.salary = original_salary
|
|
|
|
dev.save!
|
|
|
|
|
|
|
|
# Expect original salary.
|
|
|
|
dev = Developer.find(1)
|
|
|
|
assert_equal original_salary, dev.salary
|
|
|
|
end
|
2011-11-29 18:04:41 -05:00
|
|
|
Developer.connection.close
|
2005-09-29 23:39:15 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2006-06-19 18:48:51 -04:00
|
|
|
# Keep our eyes peeled.
|
|
|
|
threads << Thread.new do
|
|
|
|
10.times do
|
|
|
|
sleep 0.05
|
|
|
|
Developer.transaction do
|
|
|
|
# Always expect original salary.
|
|
|
|
assert_equal original_salary, Developer.find(1).salary
|
|
|
|
end
|
2005-09-29 23:39:15 -04:00
|
|
|
end
|
2011-11-29 18:04:41 -05:00
|
|
|
Developer.connection.close
|
2005-09-29 23:39:15 -04:00
|
|
|
end
|
2006-06-19 18:48:51 -04:00
|
|
|
|
2014-10-27 12:28:53 -04:00
|
|
|
threads.each(&:join)
|
2005-09-29 23:39:15 -04:00
|
|
|
end
|
|
|
|
|
2006-06-19 18:48:51 -04:00
|
|
|
assert_equal original_salary, Developer.find(1).salary
|
2005-09-29 23:39:15 -04:00
|
|
|
end
|
|
|
|
end
|
2004-11-23 20:04:44 -05:00
|
|
|
end
|