aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord
diff options
context:
space:
mode:
authorJeremy Kemper <jeremy@bitsweat.net>2010-06-08 14:40:20 -0400
committerJeremy Kemper <jeremy@bitsweat.net>2010-06-08 14:56:08 -0400
commit1b2941cba1165b0721f57524645fe378bee2a950 (patch)
tree40d88f07c8bed5a15304753277917750c5885ed9 /activerecord
parent32d4330b8185caa05af6ae69b0769b34b5e159eb (diff)
downloadrails-1b2941cba1165b0721f57524645fe378bee2a950.tar.gz
rails-1b2941cba1165b0721f57524645fe378bee2a950.tar.bz2
rails-1b2941cba1165b0721f57524645fe378bee2a950.zip
Temporarily revert "Update after_commit and after_rollback docs and tests to use new style API with an :on options instead of on_* suffix." and "Add after_commit and after_rollback callbacks to ActiveRecord that are called after transactions either commit or rollback on all records saved or destroyed in the transaction."
This reverts commits d2a49e4b1f30c5997e169110eed94a55aee53f56 and da840d13da865331297d5287391231b1ed39721b. [#2991] Conflicts: activerecord/CHANGELOG activerecord/lib/active_record/transactions.rb activerecord/test/cases/transaction_callbacks_test.rb
Diffstat (limited to 'activerecord')
-rw-r--r--activerecord/CHANGELOG2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb56
-rw-r--r--activerecord/lib/active_record/transactions.rb118
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb240
-rw-r--r--activerecord/test/cases/transactions_test.rb33
5 files changed, 9 insertions, 440 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index d9574b6dc7..348248e849 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -12,8 +12,6 @@
* find_or_create_by_attr(value, ...) works when attr is protected. #4457 [Santiago Pastorino, Marc-André Lafortune]
-* New callbacks: after_commit and after_rollback. Do expensive operations like image thumbnailing after_commit instead of after_save. #2991 [Brian Durand]
-
* Serialized attributes are not converted to YAML if they are any of the formats that can be serialized to XML (like Hash, Array and Strings). [José Valim]
* Destroy uses optimistic locking. If lock_version on the record you're destroying doesn't match lock_version in the database, a StaleObjectError is raised. #1966 [Curtis Hawthorne]
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 b9fb452eee..0c87e052c4 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/database_statements.rb
@@ -122,8 +122,6 @@ module ActiveRecord
requires_new = options[:requires_new] || !last_transaction_joinable
transaction_open = false
- @_current_transaction_records ||= []
-
begin
if block_given?
if requires_new || open_transactions == 0
@@ -134,7 +132,6 @@ module ActiveRecord
end
increment_open_transactions
transaction_open = true
- @_current_transaction_records.push([])
end
yield
end
@@ -144,10 +141,8 @@ module ActiveRecord
decrement_open_transactions
if open_transactions == 0
rollback_db_transaction
- rollback_transaction_records(true)
else
rollback_to_savepoint
- rollback_transaction_records(false)
end
end
raise unless database_transaction_rollback.is_a?(ActiveRecord::Rollback)
@@ -162,35 +157,20 @@ module ActiveRecord
begin
if open_transactions == 0
commit_db_transaction
- commit_transaction_records
else
release_savepoint
- save_point_records = @_current_transaction_records.pop
- unless save_point_records.blank?
- @_current_transaction_records.push([]) if @_current_transaction_records.empty?
- @_current_transaction_records.last.concat(save_point_records)
- end
end
rescue Exception => database_transaction_rollback
if open_transactions == 0
rollback_db_transaction
- rollback_transaction_records(true)
else
rollback_to_savepoint
- rollback_transaction_records(false)
end
raise
end
end
end
- # Register a record with the current transaction so that its after_commit and after_rollback callbacks
- # can be called.
- def add_transaction_record(record)
- last_batch = @_current_transaction_records.last
- last_batch << record if last_batch
- end
-
# Begins the transaction (and turns off auto-committing).
def begin_db_transaction() end
@@ -288,42 +268,6 @@ module ActiveRecord
limit.to_i
end
end
-
- # Send a rollback message to all records after they have been rolled back. If rollback
- # is false, only rollback records since the last save point.
- def rollback_transaction_records(rollback) #:nodoc
- if rollback
- records = @_current_transaction_records.flatten
- @_current_transaction_records.clear
- else
- records = @_current_transaction_records.pop
- end
-
- unless records.blank?
- records.uniq.each do |record|
- begin
- record.rolledback!(rollback)
- rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
- end
- end
- end
- end
-
- # Send a commit message to all records after they have been committed.
- def commit_transaction_records #:nodoc
- records = @_current_transaction_records.flatten
- @_current_transaction_records.clear
- unless records.blank?
- records.uniq.each do |record|
- begin
- record.committed!
- rescue Exception => e
- record.logger.error(e) if record.respond_to?(:logger)
- end
- end
- end
- end
end
end
end
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 5a8e2ce880..3f2c1911e7 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -8,11 +8,6 @@ module ActiveRecord
class TransactionError < ActiveRecordError # :nodoc:
end
- included do
- define_model_callbacks :commit, :commit_on_update, :commit_on_create, :commit_on_destroy, :only => :after
- define_model_callbacks :rollback, :rollback_on_update, :rollback_on_create, :rollback_on_destroy
- end
-
# Transactions are protective blocks where SQL statements are only permanent
# if they can all succeed as one atomic action. The classic example is a
# transfer between two accounts where you can only have a deposit if the
@@ -163,21 +158,6 @@ module ActiveRecord
# http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
# for more information about savepoints.
#
- # === Callbacks
- #
- # There are two types of callbacks associated with committing and rolling back transactions:
- # +after_commit+ and +after_rollback+.
- #
- # +after_commit+ callbacks are called on every record saved or destroyed within a
- # transaction immediately after the transaction is committed. +after_rollback+ callbacks
- # are called on every record saved or destroyed within a transaction immediately after the
- # transaction or savepoint is rolled back.
- #
- # These callbacks are useful for interacting with other systems since you will be guaranteed
- # that the callback is only executed when the database is in a permanent state. For example,
- # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
- # within a transaction could trigger the cache to be regenerated before the database is updated.
- #
# === Caveats
#
# If you're on MySQL, then do not use DDL operations in nested transactions
@@ -225,50 +205,19 @@ module ActiveRecord
# Reset id and @new_record if the transaction rolls back.
def rollback_active_record_state!
- remember_transaction_record_state
+ id_present = has_attribute?(self.class.primary_key)
+ previous_id = id
+ previous_new_record = new_record?
yield
rescue Exception
- restore_transaction_record_state
- raise
- ensure
- clear_transaction_record_state
- end
-
- # Call the after_commit callbacks
- def committed! #:nodoc:
- if transaction_record_state(:new_record)
- _run_commit_on_create_callbacks
- elsif transaction_record_state(:destroyed)
- _run_commit_on_destroy_callbacks
+ @new_record = previous_new_record
+ if id_present
+ self.id = previous_id
else
- _run_commit_on_update_callbacks
- end
- _run_commit_callbacks
- ensure
- clear_transaction_record_state
- end
-
- # Call the after rollback callbacks. The restore_state argument indicates if the record
- # state should be rolled back to the beginning or just to the last savepoint.
- def rolledback!(force_restore_state = false) #:nodoc:
- if transaction_record_state(:new_record)
- _run_rollback_on_create_callbacks
- elsif transaction_record_state(:destroyed)
- _run_rollback_on_destroy_callbacks
- else
- _run_rollback_on_update_callbacks
- end
- _run_rollback_callbacks
- ensure
- restore_transaction_record_state(force_restore_state)
- end
-
- # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks
- # can be called.
- def add_to_transaction
- if self.class.connection.add_transaction_record(self)
- remember_transaction_record_state
+ @attributes.delete(self.class.primary_key)
+ @attributes_cache.delete(self.class.primary_key)
end
+ raise
end
# Executes +method+ within a transaction and captures its return value as a
@@ -280,59 +229,10 @@ module ActiveRecord
def with_transaction_returning_status
status = nil
self.class.transaction do
- add_to_transaction
status = yield
raise ActiveRecord::Rollback unless status
end
status
end
-
- protected
-
- # Save the new record state and id of a record so it can be restored later if a transaction fails.
- def remember_transaction_record_state #:nodoc
- @_start_transaction_state ||= {}
- unless @_start_transaction_state.include?(:new_record)
- @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
- @_start_transaction_state[:new_record] = @new_record
- end
- unless @_start_transaction_state.include?(:destroyed)
- @_start_transaction_state[:destroyed] = @new_record
- end
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
- end
-
- # Clear the new record state and id of a record.
- def clear_transaction_record_state #:nodoc
- if defined?(@_start_transaction_state)
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
- remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
- end
- end
-
- # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
- def restore_transaction_record_state(force = false) #:nodoc
- if defined?(@_start_transaction_state)
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
- if @_start_transaction_state[:level] < 1
- restore_state = remove_instance_variable(:@_start_transaction_state)
- if restore_state
- @new_record = restore_state[:new_record]
- @destroyed = restore_state[:destroyed]
- if restore_state[:id]
- self.id = restore_state[:id]
- else
- @attributes.delete(self.class.primary_key)
- @attributes_cache.delete(self.class.primary_key)
- end
- end
- end
- end
- end
-
- # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
- def transaction_record_state(state) #:nodoc
- @_start_transaction_state[state] if defined?(@_start_transaction_state)
- end
end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
deleted file mode 100644
index ebc16653cb..0000000000
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ /dev/null
@@ -1,240 +0,0 @@
-require "cases/helper"
-require 'models/topic'
-require 'models/reply'
-
-class TransactionCallbacksTest < ActiveRecord::TestCase
- self.use_transactional_fixtures = false
- fixtures :topics
-
- class TopicWithCallbacks < ActiveRecord::Base
- set_table_name :topics
-
- after_commit{|record| record.send(:do_after_commit, nil)}
- after_commit(:on => :create){|record| record.send(:do_after_commit, :create)}
- after_commit(:on => :update){|record| record.send(:do_after_commit, :update)}
- after_commit(:on => :destroy){|record| record.send(:do_after_commit, :destroy)}
- after_rollback{|record| record.send(:do_after_rollback, nil)}
- after_rollback(:on => :create){|record| record.send(:do_after_rollback, :create)}
- after_rollback(:on => :update){|record| record.send(:do_after_rollback, :update)}
- after_rollback(:on => :destroy){|record| record.send(:do_after_rollback, :destroy)}
-
- def history
- @history ||= []
- end
-
- def after_commit_block(on = nil, &block)
- @after_commit ||= {}
- @after_commit[on] ||= []
- @after_commit[on] << block
- end
-
- def after_rollback_block(on = nil, &block)
- @after_rollback ||= {}
- @after_rollback[on] ||= []
- @after_rollback[on] << block
- end
-
- def do_after_commit(on)
- blocks = @after_commit[on] if defined?(@after_commit)
- blocks.each{|b| b.call(self)} if blocks
- end
-
- def do_after_rollback(on)
- blocks = @after_rollback[on] if defined?(@after_rollback)
- blocks.each{|b| b.call(self)} if blocks
- end
- end
-
- def setup
- @first, @second = TopicWithCallbacks.find(1, 3).sort_by { |t| t.id }
- end
-
- def test_call_after_commit_after_transaction_commits
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
-
- @first.save!
- assert_equal [:after_commit], @first.history
- end
-
- def test_only_call_after_commit_on_update_after_transaction_commits_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @first.save!
- assert_equal [:commit_on_update], @first.history
- end
-
- def test_only_call_after_commit_on_destroy_after_transaction_commits_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @first.destroy
- assert_equal [:commit_on_destroy], @first.history
- end
-
- def test_only_call_after_commit_on_create_after_transaction_commits_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- @new_record.save!
- assert_equal [:commit_on_create], @new_record.history
- end
-
- def test_call_after_rollback_after_transaction_rollsback
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
-
- Topic.transaction do
- @first.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:after_rollback], @first.history
- end
-
- def test_only_call_after_rollback_on_update_after_transaction_rollsback_for_existing_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- Topic.transaction do
- @first.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:rollback_on_update], @first.history
- end
-
- def test_only_call_after_rollback_on_destroy_after_transaction_rollsback_for_destroyed_record
- @first.after_commit_block(:create){|r| r.history << :commit_on_create}
- @first.after_commit_block(:update){|r| r.history << :commit_on_update}
- @first.after_commit_block(:destroy){|r| r.history << :commit_on_update}
- @first.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @first.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @first.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- Topic.transaction do
- @first.destroy
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:rollback_on_destroy], @first.history
- end
-
- def test_only_call_after_rollback_on_create_after_transaction_rollsback_for_new_record
- @new_record = TopicWithCallbacks.new(:title => "New topic", :written_on => Date.today)
- @new_record.after_commit_block(:create){|r| r.history << :commit_on_create}
- @new_record.after_commit_block(:update){|r| r.history << :commit_on_update}
- @new_record.after_commit_block(:destroy){|r| r.history << :commit_on_destroy}
- @new_record.after_rollback_block(:create){|r| r.history << :rollback_on_create}
- @new_record.after_rollback_block(:update){|r| r.history << :rollback_on_update}
- @new_record.after_rollback_block(:destroy){|r| r.history << :rollback_on_destroy}
-
- Topic.transaction do
- @new_record.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal [:rollback_on_create], @new_record.history
- end
-
- def test_call_after_rollback_when_commit_fails
- @first.connection.class.send(:alias_method, :real_method_commit_db_transaction, :commit_db_transaction)
- begin
- @first.connection.class.class_eval do
- def commit_db_transaction; raise "boom!"; end
- end
-
- @first.after_commit_block{|r| r.history << :after_commit}
- @first.after_rollback_block{|r| r.history << :after_rollback}
-
- assert !@first.save rescue nil
- assert_equal [:after_rollback], @first.history
- ensure
- @first.connection.class.send(:remove_method, :commit_db_transaction)
- @first.connection.class.send(:alias_method, :commit_db_transaction, :real_method_commit_db_transaction)
- end
- end
-
- def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint
- def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
- @first.after_rollback_block{|r| r.rollbacks(1)}
- @first.after_commit_block{|r| r.commits(1)}
-
- def @second.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @second.commits(i=0); @commits ||= 0; @commits += i if i; end
- @second.after_rollback_block{|r| r.rollbacks(1)}
- @second.after_commit_block{|r| r.commits(1)}
-
- Topic.transaction do
- @first.save!
- Topic.transaction(:requires_new => true) do
- @second.save!
- raise ActiveRecord::Rollback
- end
- end
-
- assert_equal 1, @first.commits
- assert_equal 0, @first.rollbacks
- assert_equal 0, @second.commits
- assert_equal 1, @second.rollbacks
- end
-
- def test_only_call_after_rollback_on_records_rolled_back_to_a_savepoint_when_release_savepoint_fails
- def @first.rollbacks(i=0); @rollbacks ||= 0; @rollbacks += i if i; end
- def @first.commits(i=0); @commits ||= 0; @commits += i if i; end
-
- @first.after_rollback_block{|r| r.rollbacks(1)}
- @first.after_commit_block{|r| r.commits(1)}
-
- Topic.transaction do
- @first.save
- Topic.transaction(:requires_new => true) do
- @first.save!
- raise ActiveRecord::Rollback
- end
- Topic.transaction(:requires_new => true) do
- @first.save!
- raise ActiveRecord::Rollback
- end
- end
-
- assert_equal 1, @first.commits
- assert_equal 2, @first.rollbacks
- end
-
- def test_after_transaction_callbacks_should_not_raise_errors
- def @first.last_after_transaction_error=(e); @last_transaction_error = e; end
- def @first.last_after_transaction_error; @last_transaction_error; end
- @first.after_commit_block{|r| r.last_after_transaction_error = :commit; raise "fail!";}
- @first.after_rollback_block{|r| r.last_after_transaction_error = :rollback; raise "fail!";}
-
- @first.save!
- assert_equal :commit, @first.last_after_transaction_error
-
- Topic.transaction do
- @first.save!
- raise ActiveRecord::Rollback
- end
-
- assert_equal :rollback, @first.last_after_transaction_error
- end
-end
diff --git a/activerecord/test/cases/transactions_test.rb b/activerecord/test/cases/transactions_test.rb
index 958a4e4f94..00f3b527d7 100644
--- a/activerecord/test/cases/transactions_test.rb
+++ b/activerecord/test/cases/transactions_test.rb
@@ -320,33 +320,6 @@ class TransactionTest < ActiveRecord::TestCase
end
end
- def test_restore_active_record_state_for_all_records_in_a_transaction
- topic_1 = Topic.new(:title => 'test_1')
- topic_2 = Topic.new(:title => 'test_2')
- Topic.transaction do
- assert topic_1.save
- assert topic_2.save
- @first.save
- @second.destroy
- assert_equal false, topic_1.new_record?
- assert_not_nil topic_1.id
- assert_equal false, topic_2.new_record?
- assert_not_nil topic_2.id
- assert_equal false, @first.new_record?
- assert_not_nil @first.id
- assert_equal true, @second.destroyed?
- raise ActiveRecord::Rollback
- end
-
- assert_equal true, topic_1.new_record?
- assert_nil topic_1.id
- assert_equal true, topic_2.new_record?
- assert_nil topic_2.id
- assert_equal false, @first.new_record?
- assert_not_nil @first.id
- assert_equal false, @second.destroyed?
- end
-
if current_adapter?(:PostgreSQLAdapter) && defined?(PGconn::PQTRANS_IDLE)
def test_outside_transaction_works
assert Topic.connection.outside_transaction?
@@ -409,12 +382,6 @@ class TransactionTest < ActiveRecord::TestCase
end
private
- def define_callback_method(callback_method)
- define_method(callback_method) do
- self.history << [callback_method, :method]
- end
- end
-
def add_exception_raising_after_save_callback_to_topic
Topic.class_eval <<-eoruby, __FILE__, __LINE__ + 1
remove_method(:after_save_for_transaction)