Permalink
Browse files

update test support to use savepoints rather than regular callbacks t…

…o better preserve callback order
  • Loading branch information...
1 parent 0858066 commit b5748d3118655cfaff9c1eff164c9767d3b8c834 Lars Klevan committed with Jan 15, 2010
Showing with 89 additions and 35 deletions.
  1. +6 −1 lib/after_commit.rb
  2. +78 −0 lib/after_commit/after_savepoint.rb
  3. +0 −30 lib/after_commit/test_bypass.rb
  4. +5 −4 tasks/distribution.rb
View
@@ -64,7 +64,12 @@ def self.collection(collection, connection)
require 'after_commit/active_record'
require 'after_commit/connection_adapters'
-require 'after_commit/test_bypass'
+require 'after_commit/after_savepoint'
ActiveRecord::Base.send(:include, AfterCommit::ActiveRecord)
ActiveRecord::Base.include_after_commit_extensions
+
+if defined?(RAILS_ENV) && RAILS_ENV == 'test'
+ ActiveRecord::Base.send(:include, AfterCommit::AfterSavepoint)
+ ActiveRecord::Base.include_after_savepoint_extensions
+end
@@ -0,0 +1,78 @@
+# Fix problems caused because tests all run in a single transaction.
+
+# The single transaction means that after_commit callback never happens in tests. Instead use savepoints.
+
+module AfterCommit
+ module AfterSavepoint
+ def self.included(klass)
+ klass.class_eval do
+ class << self
+ def include_after_savepoint_extensions
+ base = ::ActiveRecord::ConnectionAdapters::AbstractAdapter
+ Object.subclasses_of(base).each do |klass|
+ include_after_savepoint_extension klass
+ end
+
+ if defined?(JRUBY_VERSION) and defined?(JdbcSpec::MySQL)
+ include_after_savepoint_extension JdbcSpec::MySQL
+ end
+ end
+
+ private
+
+ def include_after_savepoint_extension(adapter)
+ additions = AfterCommit::TestConnectionAdapters
+ unless adapter.included_modules.include?(additions)
+ adapter.send :include, additions
+ end
+ end
+ end
+ end
+ end
+ end
+
+ module TestConnectionAdapters
+ def self.included(base)
+ base.class_eval do
+ def release_savepoint_with_callback
+ increment_transaction_pointer
+ committed = false
+ begin
+ trigger_before_commit_callbacks
+ trigger_before_commit_on_create_callbacks
+ trigger_before_commit_on_update_callbacks
+ trigger_before_commit_on_destroy_callbacks
+
+ release_savepoint_without_callback
+ committed = true
+
+ trigger_after_commit_callbacks
+ trigger_after_commit_on_create_callbacks
+ trigger_after_commit_on_update_callbacks
+ trigger_after_commit_on_destroy_callbacks
+ rescue
+ rollback_to_savepoint unless committed
+ ensure
+ AfterCommit.cleanup(self)
+ decrement_transaction_pointer
+ end
+ end
+ alias_method_chain :release_savepoint, :callback
+
+ # In the event the transaction fails and rolls back, nothing inside
+ # should recieve the after_commit callback, but do fire the after_rollback
+ # callback for each record that failed to be committed.
+ def rollback_to_savepoint_with_callback
+ begin
+ trigger_before_rollback_callbacks
+ rollback_to_savepoint_without_callback
+ trigger_after_rollback_callbacks
+ ensure
+ AfterCommit.cleanup(self)
+ end
+ end
+ alias_method_chain :rollback_to_savepoint, :callback
+ end
+ end
+ end
+end
@@ -1,30 +0,0 @@
-# Fix problems caused because tests all run in a single transaction.
-
-# The single transaction means that after_commit callback never happens in tests. Each of these method definitions
-# overwrites the method in the after_commit plugin that stores the callback for after the commit. In each case here
-# we simply call the callback rather than waiting for a commit that will never come.
-
-module AfterCommit::TestBypass
- def self.included(klass)
- klass.class_eval do
- [:add_committed_record_on_create, :add_committed_record_on_update, :add_committed_record_on_destroy].each do |method|
- remove_method(method)
- end
- end
- end
-
- def add_committed_record_on_create
- callback :after_commit
- callback :after_commit_on_create
- end
-
- def add_committed_record_on_update
- callback :after_commit
- callback :after_commit_on_update
- end
-
- def add_committed_record_on_destroy
- callback :after_commit
- callback :after_commit_on_destroy
- end
-end
View
@@ -11,14 +11,15 @@
end
Jeweler::Tasks.new do |gem|
- gem.name = 'after_commit'
+ gem.name = 'larsklevan-after_commit'
gem.summary = 'after_commit callback for ActiveRecord'
gem.description = %Q{
A Ruby on Rails plugin to add an after_commit callback. This can be used to trigger methods only after the entire transaction is complete.
+ Updated with savepoint support for unit testing.
}
- gem.email = "pat@freelancing-gods.com"
- gem.homepage = "http://github.com/freelancing-god/after_commit"
- gem.authors = ["Nick Muerdter", "David Yip", "Pat Allan"]
+ gem.email = "tastybyte@gmail.com"
+ gem.homepage = "http://github.com/larsklevan/after_commit"
+ gem.authors = ["Nick Muerdter", "David Yip", "Pat Allan", "Lars Klevan"]
gem.files = FileList[
'lib/**/*.rb',

2 comments on commit b5748d3

Does this work on ActiveRecord < 2.2 and earlier? I'm getting errors about release_savepoint not being defined:
undefined method release_savepoint' for classActiveRecord::ConnectionAdapters::PostgreSQLAdapter'

Owner

pat replied Oct 14, 2010

To be honest, I'm not really sure – I didn't write this code, and I still feel I don't have my head around the entire library. Lars: do you have a better idea on which versions of AR it plays nicely with?

Please sign in to comment.