after_commit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Nick Muerdter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ h1. after_commit
2
+
3
+ An ActiveRecord/Rails library to add @before_commit@, @after_commit@, @before_rollback@ and @after_rollback@ callbacks. These callbacks are focused on the transactions, instead of specific model actions. This is beneficial in situations where you are doing asynchronous processing and need committed objects.
4
+
5
+ h2. Installation
6
+
7
+ <pre><code>gem install after_commit --source http://gemcutter.org</code></pre>
8
+
9
+ h2. Usage
10
+
11
+ The following callbacks are provided:
12
+
13
+ * before_commit
14
+ * before_commit_on_create
15
+ * before_commit_on_update
16
+ * before_commit_on_destroy
17
+ * after_commit
18
+ * after_commit_on_create
19
+ * after_commit_on_update
20
+ * after_commit_on_destroy
21
+ * before_rollback
22
+ * after_rollback
23
+
24
+ You can use these just like you would any other callback:
25
+
26
+ <pre><code>class Article < ActiveRecord::Base
27
+ after_commit :method_to_call_after_commit
28
+
29
+ # ...
30
+
31
+ private
32
+
33
+ def method_to_call_after_commit
34
+ # Do something knowing that the transaction is committed.
35
+ end
36
+ end</code></pre>
37
+
38
+ h2. Credits
39
+
40
+ This code first appeared in a blog post "by Eli Miller":http://elimiller.blogspot.com/2007/06/proper-cache-expiry-with-aftercommit.html, and was then included in "Thinking Sphinx":http://ts.freelancing-gods.com by "Pat Allan":http://freelancing-gods.com, with modifications from Joost Hietbrink. The code was then "put on GitHub as a plugin":http://github.com/GUI/after_commit by Nick Muerdter, and many people forked and added their own contributions.
41
+
42
+ This version (maintained by Pat Allan) includes the following patches:
43
+
44
+ * Callbacks for specific types of actions (create, update, destroy) ("DeLynn Berry":http://delynnberry.com/)
45
+ * Fixes to extra callbacks ("Xavier Shay":http://rhnh.net/)
46
+ * Gemspec and extended tests ("David Yip":http://github.com/yipdw)
47
+ * Thread-safety ("Dmitry Galinsky":http://dima-exe.ru/)
48
+ * after_rollback callback ("Justin Balthrop":http://github.com/ninjudd)
49
+ * Test environment fix ("Pivotal Labs":http://pivotalblabs.com/)
50
+ * Scoping callbacks to specific connections ("Mat Brown":http://outofti.me/)
51
+ * before_* callbacks ("Trotter Cashion":http://trottercashion.com/)
@@ -0,0 +1,65 @@
1
+ module AfterCommit
2
+ def self.record(connection, record)
3
+ Thread.current[:committed_records] ||= {}
4
+ Thread.current[:committed_records][connection.object_id] ||= []
5
+ Thread.current[:committed_records][connection.object_id] << record
6
+ end
7
+
8
+ def self.record_created(connection, record)
9
+ Thread.current[:committed_records_on_create] ||= {}
10
+ Thread.current[:committed_records_on_create][connection.object_id] ||= []
11
+ Thread.current[:committed_records_on_create][connection.object_id] << record
12
+ end
13
+
14
+ def self.record_updated(connection, record)
15
+ Thread.current[:committed_records_on_update] ||= {}
16
+ Thread.current[:committed_records_on_update][connection.object_id] ||= []
17
+ Thread.current[:committed_records_on_update][connection.object_id] << record
18
+ end
19
+
20
+ def self.record_destroyed(connection, record)
21
+ Thread.current[:committed_records_on_destroy] ||= {}
22
+ Thread.current[:committed_records_on_destroy][connection.object_id] ||= []
23
+ Thread.current[:committed_records_on_destroy][connection.object_id] << record
24
+ end
25
+
26
+ def self.created_records(connection)
27
+ Thread.current[:committed_records_on_create] ||= {}
28
+ Thread.current[:committed_records_on_create][connection.object_id] ||= []
29
+ end
30
+
31
+ def self.updated_records(connection)
32
+ Thread.current[:committed_records_on_update] ||= {}
33
+ Thread.current[:committed_records_on_update][connection.object_id] ||= []
34
+ end
35
+
36
+ def self.destroyed_records(connection)
37
+ Thread.current[:committed_records_on_destroy] ||= {}
38
+ Thread.current[:committed_records_on_destroy][connection.object_id] ||= []
39
+ end
40
+
41
+ def self.records(connection)
42
+ Thread.current[:committed_records] ||= {}
43
+ Thread.current[:committed_records][connection.object_id] ||= []
44
+ end
45
+
46
+ def self.cleanup(connection)
47
+ Thread.current[:committed_records] = {}
48
+ Thread.current[:committed_records_on_create] = {}
49
+ Thread.current[:committed_records_on_update] = {}
50
+ Thread.current[:committed_records_on_destroy] = {}
51
+ end
52
+ end
53
+
54
+ require 'after_commit/active_record'
55
+ require 'after_commit/connection_adapters'
56
+
57
+ ActiveRecord::Base.send(:include, AfterCommit::ActiveRecord)
58
+
59
+ Object.subclasses_of(ActiveRecord::ConnectionAdapters::AbstractAdapter).each do |klass|
60
+ klass.send(:include, AfterCommit::ConnectionAdapters)
61
+ end
62
+
63
+ if defined?(JRUBY_VERSION) and defined?(JdbcSpec::MySQL)
64
+ JdbcSpec::MySQL.send :include, AfterCommit::ConnectionAdapters
65
+ end
@@ -0,0 +1,94 @@
1
+ module AfterCommit
2
+ module ActiveRecord
3
+ def self.included(base)
4
+ base.class_eval do
5
+ # The define_callbacks method was added post Rails 2.0.2 - if it
6
+ # doesn't exist, we define the callback manually
7
+ if respond_to?(:define_callbacks)
8
+ define_callbacks :after_commit,
9
+ :after_commit_on_create,
10
+ :after_commit_on_update,
11
+ :after_commit_on_destroy,
12
+ :after_rollback,
13
+ :before_commit,
14
+ :before_commit_on_create,
15
+ :before_commit_on_update,
16
+ :before_commit_on_destroy,
17
+ :before_rollback
18
+ else
19
+ class << self
20
+ # Handle after_commit callbacks - call all the registered callbacks.
21
+ def after_commit(*callbacks, &block)
22
+ callbacks << block if block_given?
23
+ write_inheritable_array(:after_commit, callbacks)
24
+ end
25
+
26
+ def after_commit_on_create(*callbacks, &block)
27
+ callbacks << block if block_given?
28
+ write_inheritable_array(:after_commit_on_create, callbacks)
29
+ end
30
+
31
+ def after_commit_on_update(*callbacks, &block)
32
+ callbacks << block if block_given?
33
+ write_inheritable_array(:after_commit_on_update, callbacks)
34
+ end
35
+
36
+ def after_commit_on_destroy(*callbacks, &block)
37
+ callbacks << block if block_given?
38
+ write_inheritable_array(:after_commit_on_destroy, callbacks)
39
+ end
40
+
41
+ def after_rollback(*callbacks, &block)
42
+ callbacks << block if block_given?
43
+ write_inheritable_array(:after_commit, callbacks)
44
+ end
45
+
46
+ def before_commit(*callbacks, &block)
47
+ callbacks << block if block_given?
48
+ write_inheritable_array(:before_commit, callbacks)
49
+ end
50
+
51
+ def before_commit_on_create(*callbacks, &block)
52
+ callbacks << block if block_given?
53
+ write_inheritable_array(:before_commit_on_create, callbacks)
54
+ end
55
+
56
+ def before_commit_on_update(*callbacks, &block)
57
+ callbacks << block if block_given?
58
+ write_inheritable_array(:before_commit_on_update, callbacks)
59
+ end
60
+
61
+ def before_commit_on_destroy(*callbacks, &block)
62
+ callbacks << block if block_given?
63
+ write_inheritable_array(:before_commit_on_destroy, callbacks)
64
+ end
65
+
66
+ def before_rollback(*callbacks, &block)
67
+ callbacks << block if block_given?
68
+ write_inheritable_array(:before_commit, callbacks)
69
+ end
70
+ end
71
+ end
72
+
73
+ after_create :add_committed_record_on_create
74
+ after_update :add_committed_record_on_update
75
+ after_destroy :add_committed_record_on_destroy
76
+
77
+ def add_committed_record_on_create
78
+ AfterCommit.record(self.class.connection, self)
79
+ AfterCommit.record_created(self.class.connection, self)
80
+ end
81
+
82
+ def add_committed_record_on_update
83
+ AfterCommit.record(self.class.connection, self)
84
+ AfterCommit.record_updated(self.class.connection, self)
85
+ end
86
+
87
+ def add_committed_record_on_destroy
88
+ AfterCommit.record(self.class.connection, self)
89
+ AfterCommit.record_destroyed(self.class.connection, self)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,140 @@
1
+ module AfterCommit
2
+ module ConnectionAdapters
3
+ def self.included(base)
4
+ base.class_eval do
5
+ # The commit_db_transaction method gets called when the outermost
6
+ # transaction finishes and everything inside commits. We want to
7
+ # override it so that after this happens, any records that were saved
8
+ # or destroyed within this transaction now get their after_commit
9
+ # callback fired.
10
+ def commit_db_transaction_with_callback
11
+ begin
12
+ trigger_before_commit_callbacks
13
+ trigger_before_commit_on_create_callbacks
14
+ trigger_before_commit_on_update_callbacks
15
+ trigger_before_commit_on_destroy_callbacks
16
+
17
+ commit_db_transaction_without_callback
18
+
19
+ trigger_after_commit_callbacks
20
+ trigger_after_commit_on_create_callbacks
21
+ trigger_after_commit_on_update_callbacks
22
+ trigger_after_commit_on_destroy_callbacks
23
+ ensure
24
+ AfterCommit.cleanup(self)
25
+ end
26
+ end
27
+ alias_method_chain :commit_db_transaction, :callback
28
+
29
+ # In the event the transaction fails and rolls back, nothing inside
30
+ # should recieve the after_commit callback, but do fire the after_rollback
31
+ # callback for each record that failed to be committed.
32
+ def rollback_db_transaction_with_callback
33
+ begin
34
+ trigger_before_rollback_callbacks
35
+ rollback_db_transaction_without_callback
36
+ trigger_after_rollback_callbacks
37
+ ensure
38
+ AfterCommit.cleanup(self)
39
+ end
40
+ end
41
+ alias_method_chain :rollback_db_transaction, :callback
42
+
43
+ protected
44
+
45
+ def trigger_before_commit_callbacks
46
+ AfterCommit.records(self).each do |record|
47
+ record.send :callback, :before_commit
48
+ end
49
+ end
50
+
51
+ def trigger_before_commit_on_create_callbacks
52
+ AfterCommit.created_records(self).each do |record|
53
+ record.send :callback, :before_commit_on_create
54
+ end
55
+ end
56
+
57
+ def trigger_before_commit_on_update_callbacks
58
+ AfterCommit.updated_records(self).each do |record|
59
+ record.send :callback, :before_commit_on_update
60
+ end
61
+ end
62
+
63
+ def trigger_before_commit_on_destroy_callbacks
64
+ AfterCommit.destroyed_records(self).each do |record|
65
+ record.send :callback, :before_commit_on_destroy
66
+ end
67
+ end
68
+
69
+ def trigger_before_rollback_callbacks
70
+ AfterCommit.records(self).each do |record|
71
+ begin
72
+ record.send :callback, :before_rollback
73
+ rescue
74
+ #
75
+ end
76
+ end
77
+ end
78
+
79
+ def trigger_after_commit_callbacks
80
+ # Trigger the after_commit callback for each of the committed
81
+ # records.
82
+ AfterCommit.records(self).each do |record|
83
+ begin
84
+ record.send :callback, :after_commit
85
+ rescue
86
+ #
87
+ end
88
+ end
89
+ end
90
+
91
+ def trigger_after_commit_on_create_callbacks
92
+ # Trigger the after_commit_on_create callback for each of the committed
93
+ # records.
94
+ AfterCommit.created_records(self).each do |record|
95
+ begin
96
+ record.send :callback, :after_commit_on_create
97
+ rescue
98
+ #
99
+ end
100
+ end
101
+ end
102
+
103
+ def trigger_after_commit_on_update_callbacks
104
+ # Trigger the after_commit_on_update callback for each of the committed
105
+ # records.
106
+ AfterCommit.updated_records(self).each do |record|
107
+ begin
108
+ record.send :callback, :after_commit_on_update
109
+ rescue
110
+ #
111
+ end
112
+ end
113
+ end
114
+
115
+ def trigger_after_commit_on_destroy_callbacks
116
+ # Trigger the after_commit_on_destroy callback for each of the committed
117
+ # records.
118
+ AfterCommit.destroyed_records(self).each do |record|
119
+ begin
120
+ record.send :callback, :after_commit_on_destroy
121
+ rescue
122
+ #
123
+ end
124
+ end
125
+ end
126
+
127
+ def trigger_after_rollback_callbacks
128
+ # Trigger the after_rollback callback for each of the committed
129
+ # records.
130
+ AfterCommit.records(self).each do |record|
131
+ begin
132
+ record.send :callback, :after_rollback
133
+ rescue
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,31 @@
1
+ # Fix problems caused because tests all run in a single transaction.
2
+
3
+ # The single transaction means that after_commit callback never happens in tests. Each of these method definitions
4
+ # overwrites the method in the after_commit plugin that stores the callback for after the commit. In each case here
5
+ # we simply call the callback rather than waiting for a commit that will never come.
6
+
7
+ module AfterCommit::TestBypass
8
+ def self.included(klass)
9
+ klass.class_eval do
10
+ [:add_committed_record, :add_committed_record_on_create, :add_committed_record_on_update, :add_committed_record_on_destroy].each do |method|
11
+ remove_method(method)
12
+ end
13
+ end
14
+ end
15
+
16
+ def add_committed_record
17
+ after_commit_callback
18
+ end
19
+
20
+ def add_committed_record_on_create
21
+ after_commit_on_create_callback
22
+ end
23
+
24
+ def add_committed_record_on_update
25
+ after_commit_on_update_callback
26
+ end
27
+
28
+ def add_committed_record_on_destroy
29
+ after_commit_on_destroy_callback
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ require 'after_commit'
2
+
3
+ ActiveRecord::Base.class_eval do
4
+ include AfterCommit::ActiveRecord
5
+ include AfterCommit::TestBypass if RAILS_ENV == 'test'
6
+ end
@@ -0,0 +1,99 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class MockRecord < ActiveRecord::Base
4
+ attr_accessor :before_commit_on_create_called
5
+ attr_accessor :before_commit_on_update_called
6
+ attr_accessor :before_commit_on_destroy_called
7
+ attr_accessor :after_commit_on_create_called
8
+ attr_accessor :after_commit_on_update_called
9
+ attr_accessor :after_commit_on_destroy_called
10
+
11
+ before_commit_on_create :do_before_create
12
+ def do_before_create
13
+ self.before_commit_on_create_called = true
14
+ end
15
+
16
+ before_commit_on_update :do_before_update
17
+ def do_before_update
18
+ self.before_commit_on_update_called = true
19
+ end
20
+
21
+ before_commit_on_create :do_before_destroy
22
+ def do_before_destroy
23
+ self.before_commit_on_destroy_called = true
24
+ end
25
+
26
+ after_commit_on_create :do_after_create
27
+ def do_after_create
28
+ self.after_commit_on_create_called = true
29
+ end
30
+
31
+ after_commit_on_update :do_after_update
32
+ def do_after_update
33
+ self.after_commit_on_update_called = true
34
+ end
35
+
36
+ after_commit_on_create :do_after_destroy
37
+ def do_after_destroy
38
+ self.after_commit_on_destroy_called = true
39
+ end
40
+ end
41
+
42
+ class UnsavableRecord < ActiveRecord::Base
43
+ attr_accessor :after_commit_called
44
+
45
+ set_table_name 'mock_records'
46
+
47
+ protected
48
+
49
+ def after_initialize
50
+ self.after_commit_called = false
51
+ end
52
+
53
+ def after_save
54
+ raise
55
+ end
56
+
57
+ after_commit :after_commit
58
+
59
+ def after_commit
60
+ self.after_commit_called = true
61
+ end
62
+ end
63
+
64
+ class AfterCommitTest < Test::Unit::TestCase
65
+ def test_before_commit_on_create_is_called
66
+ assert_equal true, MockRecord.create!.before_commit_on_create_called
67
+ end
68
+
69
+ def test_before_commit_on_update_is_called
70
+ record = MockRecord.create!
71
+ record.save
72
+ assert_equal true, record.before_commit_on_update_called
73
+ end
74
+
75
+ def test_before_commit_on_destroy_is_called
76
+ assert_equal true, MockRecord.create!.destroy.before_commit_on_destroy_called
77
+ end
78
+
79
+ def test_after_commit_on_create_is_called
80
+ assert_equal true, MockRecord.create!.after_commit_on_create_called
81
+ end
82
+
83
+ def test_after_commit_on_update_is_called
84
+ record = MockRecord.create!
85
+ record.save
86
+ assert_equal true, record.after_commit_on_update_called
87
+ end
88
+
89
+ def test_after_commit_on_destroy_is_called
90
+ assert_equal true, MockRecord.create!.destroy.after_commit_on_destroy_called
91
+ end
92
+
93
+ def test_after_commit_does_not_trigger_when_transaction_rolls_back
94
+ record = UnsavableRecord.new
95
+ begin; record.save; rescue; end
96
+
97
+ assert_equal false, record.after_commit_called
98
+ end
99
+ end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class ObservableMockRecord < ActiveRecord::Base
4
+ set_table_name 'mock_records'
5
+
6
+ attr_accessor :after_commit_called
7
+ attr_accessor :after_commit_on_create_called
8
+ attr_accessor :after_commit_on_update_called
9
+ attr_accessor :after_commit_on_destroy_called
10
+ end
11
+
12
+ class ObservableMockRecordObserver < ActiveRecord::Observer
13
+ def after_commit(model)
14
+ model.after_commit_called = true
15
+ end
16
+
17
+ def after_commit_on_create(model)
18
+ model.after_commit_on_create_called = true
19
+ end
20
+
21
+ def after_commit_on_update(model)
22
+ model.after_commit_on_update_called = true
23
+ end
24
+
25
+ def after_commit_on_destroy(model)
26
+ model.after_commit_on_destroy_called = true
27
+ end
28
+ end
29
+
30
+ class ObserverTest < Test::Unit::TestCase
31
+ def setup
32
+ ObservableMockRecord.add_observer ObservableMockRecordObserver.instance
33
+ end
34
+
35
+ def test_after_commit_is_called
36
+ record = ObservableMockRecord.create!
37
+
38
+ assert record.after_commit_called
39
+ end
40
+
41
+ def test_after_commit_on_create_is_called
42
+ record = ObservableMockRecord.create!
43
+
44
+ assert record.after_commit_on_create_called
45
+ end
46
+
47
+ def test_after_commit_on_update_is_called
48
+ record = ObservableMockRecord.create!
49
+ record.save
50
+
51
+ assert record.after_commit_on_update_called
52
+ end
53
+
54
+ def test_after_commit_on_destroy_is_called
55
+ record = ObservableMockRecord.create!.destroy
56
+
57
+ assert record.after_commit_on_destroy_called
58
+ end
59
+ end
@@ -0,0 +1,20 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
2
+ require 'test/unit'
3
+ require 'rubygems'
4
+ require 'activerecord'
5
+
6
+ begin
7
+ require 'sqlite3'
8
+ rescue LoadError
9
+ gem 'sqlite3-ruby'
10
+ retry
11
+ end
12
+
13
+ ActiveRecord::Base.establish_connection({"adapter" => "sqlite3", "database" => 'test.sqlite3'})
14
+ begin
15
+ ActiveRecord::Base.connection.execute("drop table mock_records");
16
+ rescue
17
+ end
18
+ ActiveRecord::Base.connection.execute("create table mock_records(id int)");
19
+
20
+ require 'after_commit'
metadata ADDED
@@ -0,0 +1,85 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: after_commit
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Nick Muerdter
8
+ - David Yip
9
+ - Pat Allan
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2009-11-10 00:00:00 +11:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: activerecord
19
+ type: :runtime
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: "0"
26
+ version:
27
+ - !ruby/object:Gem::Dependency
28
+ name: shoulda
29
+ type: :development
30
+ version_requirement:
31
+ version_requirements: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ version:
37
+ description: "\n 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.\n "
38
+ email: pat@freelancing-gods.com
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - LICENSE
45
+ - README.textile
46
+ files:
47
+ - LICENSE
48
+ - lib/after_commit.rb
49
+ - lib/after_commit/active_record.rb
50
+ - lib/after_commit/connection_adapters.rb
51
+ - lib/after_commit/test_bypass.rb
52
+ - rails/init.rb
53
+ - README.textile
54
+ has_rdoc: true
55
+ homepage: http://github.com/freelancing-god/after_commit
56
+ licenses: []
57
+
58
+ post_install_message:
59
+ rdoc_options:
60
+ - --charset=UTF-8
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: "0"
68
+ version:
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: "0"
74
+ version:
75
+ requirements: []
76
+
77
+ rubyforge_project:
78
+ rubygems_version: 1.3.5
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: after_commit callback for ActiveRecord
82
+ test_files:
83
+ - test/after_commit_test.rb
84
+ - test/observer_test.rb
85
+ - test/test_helper.rb