aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG8
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb7
-rw-r--r--activerecord/lib/active_record/transactions.rb11
-rw-r--r--activerecord/test/transactions_test.rb20
4 files changed, 44 insertions, 2 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index ed1b83cdca..dade34a38b 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -1,5 +1,13 @@
*SVN*
+* Added database connection as a yield parameter to ActiveRecord::Base.transaction so you can manually rollback [DHH]. Example:
+
+ transaction do |transaction|
+ david.withdrawal(100)
+ mary.deposit(100)
+ transaction.rollback! # rolls back the transaction that was otherwise going to be successful
+ end
+
* Made increment_counter/decrement_counter play nicely with optimistic locking, and added a more general update_counters method [Jamis Buck]
* Reworked David's query cache to be available as Model.cache {...}. For the duration of the block no select query should be run more then once. Any inserts/deletes/executes will flush the whole cache however [Tobias Luetke]
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
index 7d7c22c74b..1b75ab53fe 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -56,7 +56,7 @@ module ActiveRecord
begin_db_transaction
transaction_open = true
end
- yield
+ yield self
end
rescue Exception => database_transaction_rollback
if transaction_open
@@ -79,6 +79,11 @@ module ActiveRecord
# done if the transaction block raises an exception or returns false.
def rollback_db_transaction() end
+ # Alias for rollback_db_transaction to be used when yielding the transaction
+ def rollback!
+ rollback_db_transaction
+ end
+
# Alias for #add_limit_offset!.
def add_limit!(sql, options)
add_limit_offset!(sql, options) if options
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 77a8a14a2a..d9bfd331c6 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -32,6 +32,17 @@ module ActiveRecord
# Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
# that the objects by default will _not_ have their instance data returned to their pre-transactional state.
#
+ # == Rolling back a transaction manually
+ #
+ # Instead of relying on exceptions to rollback your transactions, you can also do so manually from within the scope
+ # of the transaction by accepting a yield parameter and calling rollback! on it. Example:
+ #
+ # transaction do |transaction|
+ # david.withdrawal(100)
+ # mary.deposit(100)
+ # transaction.rollback! # rolls back the transaction that was otherwise going to be successful
+ # end
+ #
# == Transactions are not distributed across database connections
#
# A transaction acts on a single database connection. If you have
diff --git a/activerecord/test/transactions_test.rb b/activerecord/test/transactions_test.rb
index 3f3ee1adb7..f20c763268 100644
--- a/activerecord/test/transactions_test.rb
+++ b/activerecord/test/transactions_test.rb
@@ -88,7 +88,7 @@ class TransactionTest < Test::Unit::TestCase
def test_failing_with_object_rollback
assert !@first.approved?, "First should be unapproved initially"
-
+
begin
assert_deprecated /Object transactions/ do
Topic.transaction(@first, @second) do
@@ -168,6 +168,24 @@ class TransactionTest < Test::Unit::TestCase
assert !Topic.find(2).approved?, "Second should have been unapproved"
end
+ def test_manually_rolling_back_a_transaction
+ Topic.transaction do |transaction|
+ @first.approved = true
+ @second.approved = false
+ @first.save
+ @second.save
+
+ transaction.rollback!
+ end
+
+ assert @first.approved?, "First should still be changed in the objects"
+ assert !@second.approved?, "Second should still be changed in the objects"
+
+ assert !Topic.find(1).approved?, "First shouldn't have been approved"
+ assert Topic.find(2).approved?, "Second should still be approved"
+ end
+
+
private
def add_exception_raising_after_save_callback_to_topic
Topic.class_eval { def after_save() raise "Make the transaction rollback" end }