diff --git a/lib/after_commit.rb b/lib/after_commit.rb index a3d27e5..eed99ab 100644 --- a/lib/after_commit.rb +++ b/lib/after_commit.rb @@ -1,33 +1,92 @@ +require 'rubygems' +require 'ruby-debug' +Debugger.start module AfterCommit + @@records = {} + @@records_queue = [] + + @@record_methods = { + :all => :committed_records, + :create => :committed_records_on_create, + :update => :committed_records_on_update, + :destroy => :committed_records_on_destroy + } + + @@callback_methods = { + :all => :after_commit_callback, + :create => :after_commit_on_create_callback, + :update => :after_commit_on_update_callback, + :destroy => :after_commit_on_destroy_callback + } + + def self.records + @@records + end + + # Push a new variable holder onto our stack. + def self.push + @@records_queue.push @@records + @@records = {} + end + + def self.pop + @@records = @@records_queue.pop + end + + # Send callbacks of this type after a commit. + # We push a new variable holder on each record, then pop it off, which avoids + # an infinite loop whereby an on_commit callback makes a new transaction + # (like in creating a BackgrounDRb record) + def self.callback(crud_method, &block) + record_method = @@record_methods[crud_method] + callback_method = @@callback_methods[crud_method] + committed_records = send(record_method) + + unless committed_records.empty? + committed_records.each do |record| + push + record.send(callback_method) + pop + end + end + + records[crud_method] = [] + end + + def self.clear! + @@records = {} + @@records_queue = [] + end + def self.committed_records - @@committed_records ||= [] + records[:all] ||= [] end def self.committed_records=(committed_records) - @@committed_records = committed_records + records[:all] = committed_records end def self.committed_records_on_create - @@committed_records_on_create ||= [] + records[:create] ||= [] end def self.committed_records_on_create=(committed_records) - @@committed_records_on_create = committed_records + records[:create] = committed_records end def self.committed_records_on_update - @@committed_records_on_update ||= [] + records[:update] ||= [] end def self.committed_records_on_update=(committed_records) - @@committed_records_on_update = committed_records + records[:update] = committed_records end def self.committed_records_on_destroy - @@committed_records_on_destroy ||= [] + records[:destroy] ||= [] end def self.committed_records_on_destroy=(committed_records) - @@committed_records_on_destroy = committed_records + records[:destroy] = committed_records end end diff --git a/lib/after_commit/connection_adapters.rb b/lib/after_commit/connection_adapters.rb index 02bea17..4655465 100644 --- a/lib/after_commit/connection_adapters.rb +++ b/lib/after_commit/connection_adapters.rb @@ -21,80 +21,26 @@ def commit_db_transaction_with_callback def rollback_db_transaction_with_callback rollback_db_transaction_without_callback - AfterCommit.committed_records = [] - AfterCommit.committed_records_on_create = [] - AfterCommit.committed_records_on_update = [] - AfterCommit.committed_records_on_destroy = [] + AfterCommit.clear! end alias_method_chain :rollback_db_transaction, :callback - protected + protected + def trigger_after_commit_callbacks - # Trigger the after_commit callback for each of the committed - # records. - if AfterCommit.committed_records.any? - AfterCommit.committed_records.each do |record| - begin - record.after_commit_callback - rescue - end - end - end - - # Make sure we clear out our list of committed records now that we've - # triggered the callbacks for each one. - AfterCommit.committed_records = [] + AfterCommit.callback(:all) end def trigger_after_commit_on_create_callbacks - # Trigger the after_commit_on_create callback for each of the committed - # records. - if AfterCommit.committed_records_on_create.any? - AfterCommit.committed_records_on_create.each do |record| - begin - record.after_commit_on_create_callback - rescue - end - end - end - - # Make sure we clear out our list of committed records now that we've - # triggered the callbacks for each one. - AfterCommit.committed_records_on_create = [] + AfterCommit.callback(:create) end def trigger_after_commit_on_update_callbacks - # Trigger the after_commit_on_update callback for each of the committed - # records. - if AfterCommit.committed_records_on_update.any? - AfterCommit.committed_records_on_update.each do |record| - begin - record.after_commit_on_update_callback - rescue - end - end - end - - # Make sure we clear out our list of committed records now that we've - # triggered the callbacks for each one. - AfterCommit.committed_records_on_update = [] + AfterCommit.callback(:update) end def trigger_after_commit_on_destroy_callbacks - # Trigger the after_commit_on_destroy callback for each of the committed - # records. - if AfterCommit.committed_records_on_destroy.any? - AfterCommit.committed_records_on_destroy.each do |record| - begin - record.after_commit_on_destroy_callback - rescue - end - end - end - - # Make sure we clear out our list of committed records now that we've - # triggered the callbacks for each one. - AfterCommit.committed_records_on_destroy = [] + AfterCommit.callback(:destroy) end #end protected end diff --git a/test/after_commit_test.rb b/test/after_commit_test.rb index f8b42cd..550e9a2 100644 --- a/test/after_commit_test.rb +++ b/test/after_commit_test.rb @@ -8,31 +8,53 @@ ActiveRecord::Base.establish_connection({"adapter" => "sqlite3", "database" => 'test.sqlite3'}) begin - ActiveRecord::Base.connection.execute("drop table mock_records"); + ActiveRecord::Base.connection.execute("drop table mock_records") + ActiveRecord::Base.connection.execute("drop table mock_non_callbacks") rescue end -ActiveRecord::Base.connection.execute("create table mock_records(id int)"); +ActiveRecord::Base.connection.execute("create table mock_records(id int)") +ActiveRecord::Base.connection.execute("create table mock_non_callbacks(id int)") require File.dirname(__FILE__) + '/../init.rb' +class MockNonCallback < ActiveRecord::Base; end + class MockRecord < ActiveRecord::Base + attr_accessor :after_commit_called attr_accessor :after_commit_on_create_called attr_accessor :after_commit_on_update_called attr_accessor :after_commit_on_destroy_called + + def clear_flags + @after_commit_called = @after_commit_on_create_called = @after_commit_on_update_called = @after_commit_on_destroy_called = nil + end + + after_commit :do_commit + def do_commit + raise "Re-called on commit!" if self.after_commit_called + self.after_commit_called = true + MockNonCallback.transaction{ MockNonCallback.create! } + end after_commit_on_create :do_create def do_create + raise "Re-called on create!" if self.after_commit_on_create_called self.after_commit_on_create_called = true + MockNonCallback.transaction{ MockNonCallback.create! } end after_commit_on_update :do_update def do_update + raise "Re-called on update!" if self.after_commit_on_update_called self.after_commit_on_update_called = true + MockNonCallback.transaction{ MockNonCallback.create! } end - after_commit_on_create :do_destroy + after_commit_on_destroy :do_destroy def do_destroy + raise "Re-called on destroy!" if self.after_commit_on_destroy_called self.after_commit_on_destroy_called = true + MockNonCallback.transaction{ MockNonCallback.create! } end end @@ -43,11 +65,14 @@ def test_after_commit_on_create_is_called def test_after_commit_on_update_is_called record = MockRecord.create! + record.clear_flags record.save assert_equal true, record.after_commit_on_update_called end def test_after_commit_on_destroy_is_called - assert_equal true, MockRecord.create!.destroy.after_commit_on_destroy_called + record = MockRecord.create! + record.clear_flags + assert_equal true, record.destroy.after_commit_on_destroy_called end end