aleksi-after_commit 1.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,64 @@
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
+ *Please note*: Rails 3 (and thus, ActiveRecord 3) "provides commit callbacks natively":http://github.com/rails/rails/commit/da840d13da865331297d5287391231b1ed39721b, so this library will remain just for versions 1.2.x to 2.x of ActiveRecord.
6
+
7
+ h2. Installation
8
+
9
+ <pre><code>gem install after_commit</code></pre>
10
+
11
+ h2. Usage
12
+
13
+ The following callbacks are provided:
14
+
15
+ * before_commit
16
+ * before_commit_on_create
17
+ * before_commit_on_update
18
+ * before_commit_on_destroy
19
+ * after_commit
20
+ * after_commit_on_create
21
+ * after_commit_on_update
22
+ * after_commit_on_destroy
23
+ * before_rollback
24
+ * after_rollback
25
+
26
+ You can use these just like you would any other callback:
27
+
28
+ <pre><code>class Article < ActiveRecord::Base
29
+ after_commit :method_to_call_after_commit
30
+
31
+ # ...
32
+
33
+ private
34
+
35
+ def method_to_call_after_commit
36
+ # Do something knowing that the transaction is committed.
37
+ end
38
+ end</code></pre>
39
+
40
+ h2. In Tests
41
+
42
+ Keep in mind that transactions are finicky at best in tests, and so there's a helper module to make @after_commit@ play nicely in your testing context. You'll need to add these two lines to your spec/test helper:
43
+
44
+ <pre><code>ActiveRecord::Base.send(:include, AfterCommit::AfterSavepoint)
45
+ ActiveRecord::Base.include_after_savepoint_extensions</code></pre>
46
+
47
+ h2. Credits
48
+
49
+ 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.
50
+
51
+ This version (maintained by Pat Allan) includes the following patches:
52
+
53
+ * Callbacks for specific types of actions (create, update, destroy) ("DeLynn Berry":http://delynnberry.com/)
54
+ * Fixes to extra callbacks ("Xavier Shay":http://rhnh.net/)
55
+ * Thread-safety ("Dmitry Galinsky":http://dima-exe.ru/)
56
+ * after_rollback callback ("Justin Balthrop":http://github.com/ninjudd)
57
+ * Test environment fix ("Pivotal Labs":http://pivotalblabs.com/)
58
+ * Scoping callbacks to specific connections ("Mat Brown":http://outofti.me/)
59
+ * before_* callbacks ("Trotter Cashion":http://trottercashion.com/)
60
+ * Gemspec and extended tests, works as a plugin, doesn't load ActiveRecord hooks twice, doesn't add Test environment hook automatically. ("David Yip":http://github.com/yipdw)
61
+ * Exception propagation ("George Ogata":http://github.com/oggy/)
62
+ * Keeping callbacks focused on the correct transactions ("Benjamin Stein":http://benjaminste.in/)
63
+ * Using savepoints for test helper ("Lars Klevan":http://www.linkedin.com/in/larsklevan and "Joel Chippindale":http://blog.monkeysthumb.org/)
64
+ * Only tracking objects from classes that have appropriate callbacks; Adding @after/before_commit_on_save@ to cover creates and updates but not destroys ("Jason Weathered":http://jasoncodes.com/)
@@ -0,0 +1,81 @@
1
+ module AfterCommit
2
+ def self.record(connection, record)
3
+ prepare_collection :committed_records, connection
4
+ add_to_collection :committed_records, connection, record
5
+ end
6
+
7
+ def self.record_created(connection, record)
8
+ prepare_collection :committed_records_on_create, connection
9
+ add_to_collection :committed_records_on_create, connection, record
10
+ end
11
+
12
+ def self.record_updated(connection, record)
13
+ prepare_collection :committed_records_on_update, connection
14
+ add_to_collection :committed_records_on_update, connection, record
15
+ end
16
+
17
+ def self.record_saved(connection, record)
18
+ prepare_collection :committed_records_on_save, connection
19
+ add_to_collection :committed_records_on_save, connection, record
20
+ end
21
+
22
+ def self.record_destroyed(connection, record)
23
+ prepare_collection :committed_records_on_destroy, connection
24
+ add_to_collection :committed_records_on_destroy, connection, record
25
+ end
26
+
27
+ def self.records(connection)
28
+ collection :committed_records, connection
29
+ end
30
+
31
+ def self.created_records(connection)
32
+ collection :committed_records_on_create, connection
33
+ end
34
+
35
+ def self.updated_records(connection)
36
+ collection :committed_records_on_update, connection
37
+ end
38
+
39
+ def self.saved_records(connection)
40
+ collection :committed_records_on_save, connection
41
+ end
42
+
43
+ def self.destroyed_records(connection)
44
+ collection :committed_records_on_destroy, connection
45
+ end
46
+
47
+ def self.cleanup(connection)
48
+ [
49
+ :committed_records,
50
+ :committed_records_on_create,
51
+ :committed_records_on_update,
52
+ :committed_records_on_save,
53
+ :committed_records_on_destroy
54
+ ].each do |collection|
55
+ Thread.current[collection] ||= {}
56
+ Thread.current[collection][connection.old_transaction_key] = []
57
+ end
58
+ end
59
+
60
+ def self.prepare_collection(collection, connection)
61
+ Thread.current[collection] ||= {}
62
+ Thread.current[collection][connection.unique_transaction_key] ||= []
63
+ end
64
+
65
+ def self.add_to_collection(collection, connection, record)
66
+ Thread.current[collection][connection.unique_transaction_key] << record
67
+ end
68
+
69
+ def self.collection(collection, connection)
70
+ Thread.current[collection] ||= {}
71
+ Thread.current[collection][connection.old_transaction_key] ||= []
72
+ end
73
+ end
74
+
75
+ require 'after_commit/active_support_callbacks'
76
+ require 'after_commit/active_record'
77
+ require 'after_commit/connection_adapters'
78
+ require 'after_commit/after_savepoint'
79
+
80
+ ActiveRecord::Base.send(:include, AfterCommit::ActiveRecord)
81
+ ActiveRecord::Base.include_after_commit_extensions
@@ -0,0 +1,87 @@
1
+ module AfterCommit
2
+ module ActiveRecord
3
+ def self.included(base)
4
+ base.class_eval do
5
+ class << self
6
+ def establish_connection_with_after_commit(spec = nil)
7
+ result = establish_connection_without_after_commit spec
8
+ include_after_commit_extensions
9
+ result
10
+ end
11
+ alias_method_chain :establish_connection, :after_commit
12
+
13
+ def include_after_commit_extensions
14
+ base = ::ActiveRecord::ConnectionAdapters::AbstractAdapter
15
+ Object.subclasses_of(base).each do |klass|
16
+ include_after_commit_extension klass
17
+ end
18
+
19
+ if defined?(JRUBY_VERSION) and defined?(JdbcSpec::MySQL)
20
+ include_after_commit_extension JdbcSpec::MySQL
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def include_after_commit_extension(adapter)
27
+ additions = AfterCommit::ConnectionAdapters
28
+ unless adapter.included_modules.include?(additions)
29
+ adapter.send :include, additions
30
+ end
31
+ end
32
+ end
33
+
34
+ define_callbacks :after_commit,
35
+ :after_commit_on_create,
36
+ :after_commit_on_update,
37
+ :after_commit_on_save,
38
+ :after_commit_on_destroy,
39
+ :after_rollback,
40
+ :before_commit,
41
+ :before_commit_on_create,
42
+ :before_commit_on_update,
43
+ :before_commit_on_save,
44
+ :before_commit_on_destroy,
45
+ :before_rollback
46
+
47
+ after_create :add_committed_record_on_create
48
+ after_update :add_committed_record_on_update
49
+ after_save :add_committed_record_on_save
50
+ after_destroy :add_committed_record_on_destroy
51
+
52
+ def add_committed_record
53
+ if have_callback? :before_commit, :after_commit, :before_rollback, :after_rollback
54
+ AfterCommit.record(self.class.connection, self)
55
+ end
56
+ end
57
+
58
+ def add_committed_record_on_create
59
+ add_committed_record
60
+ if have_callback? :before_commit_on_create, :after_commit_on_create
61
+ AfterCommit.record_created(self.class.connection, self)
62
+ end
63
+ end
64
+
65
+ def add_committed_record_on_update
66
+ add_committed_record
67
+ if have_callback? :before_commit_on_update, :after_commit_on_update
68
+ AfterCommit.record_updated(self.class.connection, self)
69
+ end
70
+ end
71
+
72
+ def add_committed_record_on_save
73
+ if have_callback? :before_commit_on_save, :after_commit_on_save
74
+ AfterCommit.record_saved(self.class.connection, self)
75
+ end
76
+ end
77
+
78
+ def add_committed_record_on_destroy
79
+ add_committed_record
80
+ if have_callback? :before_commit_on_destroy, :after_commit_on_destroy
81
+ AfterCommit.record_destroyed(self.class.connection, self)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,59 @@
1
+ if defined? ActiveSupport::Callbacks
2
+
3
+ module AfterCommit
4
+ module ActiveSupportCallbacks
5
+ def self.included(base)
6
+
7
+ base::Callback.class_eval do
8
+ def have_callback?
9
+ true
10
+ end
11
+ end
12
+
13
+ base::CallbackChain.class_eval do
14
+ def have_callback?
15
+ any? &:have_callback?
16
+ end
17
+ end
18
+
19
+ base.class_eval do
20
+ def have_callback?(*callbacks)
21
+ self.class.observers.size > 0 or
22
+ self.class.count_observers > 0 or
23
+ callbacks.any? do |callback|
24
+ self.class.send("#{callback}_callback_chain").have_callback?
25
+ end
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ ActiveSupport::Callbacks.send(:include, AfterCommit::ActiveSupportCallbacks)
33
+
34
+ else
35
+
36
+ class ActiveRecord::Base
37
+
38
+ def self.define_callbacks(*names)
39
+ names.each do |name|
40
+ instance_eval <<-RUBY
41
+ def #{name}(*callbacks, &block)
42
+ callbacks << block if block_given?
43
+ write_inheritable_array(:#{name}, callbacks)
44
+ end
45
+ RUBY
46
+ end
47
+ end
48
+
49
+ def have_callback?(*names)
50
+ self.class.observers.size > 0 or
51
+ self.class.count_observers > 0 or
52
+ names.any? do |name|
53
+ !self.class.read_inheritable_attribute(name).blank?
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,85 @@
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. Instead use savepoints.
4
+
5
+ module AfterCommit
6
+ module AfterSavepoint
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ class << self
10
+ def include_after_savepoint_extensions
11
+ base = ::ActiveRecord::ConnectionAdapters::AbstractAdapter
12
+ Object.subclasses_of(base).each do |klass|
13
+ include_after_savepoint_extension klass
14
+ end
15
+
16
+ if defined?(JRUBY_VERSION) and defined?(JdbcSpec::MySQL)
17
+ include_after_savepoint_extension JdbcSpec::MySQL
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def include_after_savepoint_extension(adapter)
24
+ additions = AfterCommit::TestConnectionAdapters
25
+ unless adapter.included_modules.include?(additions)
26
+ adapter.send :include, additions
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ module TestConnectionAdapters
35
+ def self.included(base)
36
+ base.class_eval do
37
+
38
+ # matches commit_db_transaction_with_callback
39
+ def release_savepoint_with_callback
40
+ increment_transaction_pointer
41
+ result = nil
42
+ begin
43
+ trigger_before_commit_callbacks
44
+ trigger_before_commit_on_create_callbacks
45
+ trigger_before_commit_on_update_callbacks
46
+ trigger_before_commit_on_save_callbacks
47
+ trigger_before_commit_on_destroy_callbacks
48
+
49
+ result = release_savepoint_without_callback
50
+ @disable_rollback = true
51
+
52
+ trigger_after_commit_callbacks
53
+ trigger_after_commit_on_create_callbacks
54
+ trigger_after_commit_on_update_callbacks
55
+ trigger_after_commit_on_save_callbacks
56
+ trigger_after_commit_on_destroy_callbacks
57
+ result
58
+ ensure
59
+ AfterCommit.cleanup(self)
60
+ decrement_transaction_pointer
61
+ end
62
+ end
63
+ alias_method_chain :release_savepoint, :callback
64
+
65
+ # matches rollback_db_transaction_with_callback
66
+ def rollback_to_savepoint_with_callback
67
+ return if @disable_rollback
68
+ increment_transaction_pointer
69
+ begin
70
+ result = nil
71
+ trigger_before_rollback_callbacks
72
+ result = rollback_to_savepoint_without_callback
73
+ trigger_after_rollback_callbacks
74
+ result
75
+ ensure
76
+ AfterCommit.cleanup(self)
77
+ decrement_transaction_pointer
78
+ end
79
+ decrement_transaction_pointer
80
+ end
81
+ alias_method_chain :rollback_to_savepoint, :callback
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,179 @@
1
+ module AfterCommit
2
+ module ConnectionAdapters
3
+ def self.included(base)
4
+ base.class_eval do
5
+ def transaction_with_callback(*args, &block)
6
+ # @disable_rollback is set to false at the start of the
7
+ # outermost call to #transaction. After committing, it is
8
+ # set to true to prevent exceptions causing a spurious
9
+ # rollback.
10
+ outermost_call = @disable_rollback.nil?
11
+ @disable_rollback = false if outermost_call
12
+ transaction_without_callback(*args, &block)
13
+ ensure
14
+ @disable_rollback = nil if outermost_call
15
+ end
16
+ alias_method_chain :transaction, :callback
17
+
18
+ # The commit_db_transaction method gets called when the outermost
19
+ # transaction finishes and everything inside commits. We want to
20
+ # override it so that after this happens, any records that were saved
21
+ # or destroyed within this transaction now get their after_commit
22
+ # callback fired.
23
+ def commit_db_transaction_with_callback
24
+ increment_transaction_pointer
25
+ result = nil
26
+ begin
27
+ trigger_before_commit_callbacks
28
+ trigger_before_commit_on_create_callbacks
29
+ trigger_before_commit_on_update_callbacks
30
+ trigger_before_commit_on_save_callbacks
31
+ trigger_before_commit_on_destroy_callbacks
32
+
33
+ result = commit_db_transaction_without_callback
34
+ @disable_rollback = true
35
+
36
+ trigger_after_commit_callbacks
37
+ trigger_after_commit_on_create_callbacks
38
+ trigger_after_commit_on_update_callbacks
39
+ trigger_after_commit_on_save_callbacks
40
+ trigger_after_commit_on_destroy_callbacks
41
+ result
42
+ ensure
43
+ AfterCommit.cleanup(self)
44
+ decrement_transaction_pointer
45
+ end
46
+ end
47
+ alias_method_chain :commit_db_transaction, :callback
48
+
49
+ # In the event the transaction fails and rolls back, nothing inside
50
+ # should recieve the after_commit callback, but do fire the after_rollback
51
+ # callback for each record that failed to be committed.
52
+ def rollback_db_transaction_with_callback
53
+ return if @disable_rollback
54
+ increment_transaction_pointer
55
+ begin
56
+ result = nil
57
+ trigger_before_rollback_callbacks
58
+ result = rollback_db_transaction_without_callback
59
+ trigger_after_rollback_callbacks
60
+ result
61
+ ensure
62
+ AfterCommit.cleanup(self)
63
+ decrement_transaction_pointer
64
+ end
65
+ decrement_transaction_pointer
66
+ end
67
+ alias_method_chain :rollback_db_transaction, :callback
68
+
69
+ def unique_transaction_key
70
+ [object_id, transaction_pointer]
71
+ end
72
+
73
+ def old_transaction_key
74
+ [object_id, transaction_pointer - 1]
75
+ end
76
+
77
+ protected
78
+
79
+ def trigger_before_commit_callbacks
80
+ AfterCommit.records(self).each do |record|
81
+ record.send :callback, :before_commit
82
+ end
83
+ end
84
+
85
+ def trigger_before_commit_on_create_callbacks
86
+ AfterCommit.created_records(self).each do |record|
87
+ record.send :callback, :before_commit_on_create
88
+ end
89
+ end
90
+
91
+ def trigger_before_commit_on_update_callbacks
92
+ AfterCommit.updated_records(self).each do |record|
93
+ record.send :callback, :before_commit_on_update
94
+ end
95
+ end
96
+
97
+ def trigger_before_commit_on_save_callbacks
98
+ AfterCommit.saved_records(self).each do |record|
99
+ record.send :callback, :before_commit_on_save
100
+ end
101
+ end
102
+
103
+ def trigger_before_commit_on_destroy_callbacks
104
+ AfterCommit.destroyed_records(self).each do |record|
105
+ record.send :callback, :before_commit_on_destroy
106
+ end
107
+ end
108
+
109
+ def trigger_before_rollback_callbacks
110
+ AfterCommit.records(self).each do |record|
111
+ record.send :callback, :before_rollback
112
+ end
113
+ end
114
+
115
+ def trigger_after_commit_callbacks
116
+ # Trigger the after_commit callback for each of the committed
117
+ # records.
118
+ AfterCommit.records(self).each do |record|
119
+ record.send :callback, :after_commit
120
+ end
121
+ end
122
+
123
+ def trigger_after_commit_on_create_callbacks
124
+ # Trigger the after_commit_on_create callback for each of the committed
125
+ # records.
126
+ AfterCommit.created_records(self).each do |record|
127
+ record.send :callback, :after_commit_on_create
128
+ end
129
+ end
130
+
131
+ def trigger_after_commit_on_update_callbacks
132
+ # Trigger the after_commit_on_update callback for each of the committed
133
+ # records.
134
+ AfterCommit.updated_records(self).each do |record|
135
+ record.send :callback, :after_commit_on_update
136
+ end
137
+ end
138
+
139
+ def trigger_after_commit_on_save_callbacks
140
+ # Trigger the after_commit_on_save callback for each of the committed
141
+ # records.
142
+ AfterCommit.saved_records(self).each do |record|
143
+ record.send :callback, :after_commit_on_save
144
+ end
145
+ end
146
+
147
+ def trigger_after_commit_on_destroy_callbacks
148
+ # Trigger the after_commit_on_destroy callback for each of the committed
149
+ # records.
150
+ AfterCommit.destroyed_records(self).each do |record|
151
+ record.send :callback, :after_commit_on_destroy
152
+ end
153
+ end
154
+
155
+ def trigger_after_rollback_callbacks
156
+ # Trigger the after_rollback callback for each of the committed
157
+ # records.
158
+ AfterCommit.records(self).each do |record|
159
+ record.send :callback, :after_rollback
160
+ end
161
+ end
162
+
163
+ def transaction_pointer
164
+ Thread.current[:after_commit_pointer] ||= 0
165
+ end
166
+
167
+ def increment_transaction_pointer
168
+ Thread.current[:after_commit_pointer] ||= 0
169
+ Thread.current[:after_commit_pointer] += 1
170
+ end
171
+
172
+ def decrement_transaction_pointer
173
+ Thread.current[:after_commit_pointer] ||= 0
174
+ Thread.current[:after_commit_pointer] -= 1
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,282 @@
1
+ require 'test_helper'
2
+
3
+ class MockRecord < ActiveRecord::Base
4
+
5
+ PHASES = %w(before after)
6
+ ACTIONS = %w(create update save destroy)
7
+
8
+ PHASES.each do |phase|
9
+ ACTIONS.each do |action|
10
+
11
+ class_eval <<-RUBY
12
+
13
+ attr_accessor :#{phase}_commit_on_#{action}_called
14
+
15
+ #{phase}_commit_on_#{action} :do_#{phase}_#{action}
16
+ def do_#{phase}_#{action}
17
+ self.#{phase}_commit_on_#{action}_called = true
18
+ end
19
+
20
+ RUBY
21
+
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ class CountingRecord < ActiveRecord::Base
28
+ attr_accessor :after_commit_on_create_called
29
+ cattr_accessor :counter
30
+ @@counter=0
31
+
32
+ after_commit_on_create :do_after_create
33
+ def do_after_create
34
+ @@counter+=1
35
+ end
36
+ end
37
+
38
+ class Foo < ActiveRecord::Base
39
+ attr_reader :creating
40
+
41
+ after_commit :create_bar
42
+
43
+ private
44
+
45
+ def create_bar
46
+ @creating ||= 0
47
+ @creating += 1
48
+
49
+ raise Exception, 'looping' if @creating > 1
50
+ Bar.create
51
+ end
52
+ end
53
+
54
+ class Bar < ActiveRecord::Base
55
+ #
56
+ end
57
+
58
+ class UnsavableRecord < ActiveRecord::Base
59
+ attr_accessor :after_commit_called, :after_rollback_called
60
+
61
+ set_table_name 'mock_records'
62
+
63
+ protected
64
+
65
+ def after_initialize
66
+ self.after_commit_called = false
67
+ end
68
+
69
+ def after_save
70
+ raise
71
+ end
72
+
73
+ after_commit :after_commit
74
+ def after_commit
75
+ self.after_commit_called = true
76
+ end
77
+
78
+ after_rollback :after_rollback
79
+ def after_rollback
80
+ self.after_rollback_called = true
81
+ end
82
+ end
83
+
84
+ class TrackCountRecord < ActiveRecord::Base
85
+ attr_accessor :total_count
86
+
87
+ set_table_name 'mock_records'
88
+
89
+ protected
90
+
91
+ after_commit :update_counts
92
+ after_rollback :update_counts
93
+
94
+ def update_counts
95
+ all_records =
96
+ AfterCommit.records(connection) +
97
+ AfterCommit.created_records(connection) +
98
+ AfterCommit.updated_records(connection) +
99
+ AfterCommit.saved_records(connection) +
100
+ AfterCommit.destroyed_records(connection)
101
+ all_records.uniq!
102
+ self.total_count = all_records.size
103
+ end
104
+ end
105
+
106
+ class AfterCommitTest < Test::Unit::TestCase
107
+ def test_before_commit_on_create_is_called
108
+ assert_equal true, MockRecord.create!.before_commit_on_create_called
109
+ end
110
+
111
+ def test_before_commit_on_update_is_called
112
+ record = MockRecord.create!
113
+ record.before_commit_on_update_called = false
114
+ record.save
115
+ assert_equal true, record.before_commit_on_update_called
116
+ end
117
+
118
+ def test_before_commit_on_update_is_not_called_for_create
119
+ assert_nil MockRecord.create!.before_commit_on_update_called
120
+ end
121
+
122
+ def test_before_commit_on_save_is_called_for_create
123
+ assert_equal true, MockRecord.create!.before_commit_on_save_called
124
+ end
125
+
126
+ def test_before_commit_on_save_is_called_for_update
127
+ record = MockRecord.create!
128
+ record.before_commit_on_save_called = false
129
+ record.save
130
+ assert_equal true, record.before_commit_on_save_called
131
+ end
132
+
133
+ def test_before_commit_on_destroy_is_called
134
+ assert_equal true, MockRecord.create!.destroy.before_commit_on_destroy_called
135
+ end
136
+
137
+ def test_after_commit_on_create_is_called
138
+ assert_equal true, MockRecord.create!.after_commit_on_create_called
139
+ end
140
+
141
+ def test_after_commit_on_update_is_called
142
+ record = MockRecord.create!
143
+ record.after_commit_on_update_called = false
144
+ record.save
145
+ assert_equal true, record.after_commit_on_update_called
146
+ end
147
+
148
+ def test_after_commit_on_update_is_not_called_for_create
149
+ assert_nil MockRecord.create!.after_commit_on_update_called
150
+ end
151
+
152
+ def test_after_commit_on_save_is_called_for_create
153
+ assert_equal true, MockRecord.create!.after_commit_on_save_called
154
+ end
155
+
156
+ def test_after_commit_on_save_is_called_for_update
157
+ record = MockRecord.create!
158
+ record.after_commit_on_save_called = false
159
+ record.save
160
+ assert_equal true, record.after_commit_on_save_called
161
+ end
162
+
163
+ def test_after_commit_on_destroy_is_called
164
+ assert_equal true, MockRecord.create!.destroy.after_commit_on_destroy_called
165
+ end
166
+
167
+ def test_after_commit_does_not_trigger_when_transaction_rolls_back
168
+ record = UnsavableRecord.new
169
+ begin; record.save; rescue; end
170
+
171
+ # Ensure that commit of another transaction doesn't then trigger the
172
+ # after_commit hook on previously rolled back record
173
+ another_record = MockRecord.create!
174
+ another_record.save
175
+
176
+ assert_equal false, record.after_commit_called
177
+ end
178
+
179
+ def test_after_commit_does_not_trigger_when_unrelated_transaction_commits
180
+ begin
181
+ CountingRecord.transaction do
182
+ CountingRecord.create!
183
+ raise "fail"
184
+ end
185
+ rescue
186
+ end
187
+ assert_equal 0, CountingRecord.counter
188
+ CountingRecord.create!
189
+ assert_equal 1, CountingRecord.counter
190
+ end
191
+
192
+ def test_after_rollback_triggered_when_transaction_rolls_back
193
+ record = UnsavableRecord.new
194
+ begin; record.save; rescue; end
195
+
196
+ assert record.after_rollback_called
197
+ end
198
+
199
+ def test_two_transactions_are_separate
200
+ Bar.delete_all
201
+ foo = Foo.create
202
+
203
+ assert_equal 1, foo.creating
204
+ end
205
+
206
+ def test_records_with_callbacks_are_tracked
207
+ record = nil
208
+ MockRecord.transaction do
209
+ record = TrackCountRecord.create!
210
+ 5.times.each { MockRecord.create! }
211
+ end
212
+ assert_equal 6, record.total_count
213
+ end
214
+
215
+ def test_records_without_callbacks_are_not_tracked
216
+ record = nil
217
+ MockRecord.transaction do
218
+ record = TrackCountRecord.create!
219
+ 5.times.each { Bar.create! }
220
+ end
221
+ assert_equal 1, record.total_count
222
+ end
223
+
224
+ TestError = Class.new(StandardError)
225
+
226
+ def test_exceptions_in_before_commit_on_create_are_not_swallowed
227
+ record = MockRecord.new
228
+ def record.do_before_create
229
+ raise TestError, 'catch me!'
230
+ end
231
+ assert_raises(TestError){record.save!}
232
+ end
233
+
234
+ def test_exceptions_in_after_commit_on_create_are_not_swallowed
235
+ record = MockRecord.new
236
+ def record.do_after_create
237
+ raise TestError, 'catch me!'
238
+ end
239
+ assert_raises(TestError){record.save!}
240
+ end
241
+
242
+ def test_exceptions_in_before_commit_on_update_are_not_swallowed
243
+ record = MockRecord.create
244
+ def record.do_before_update
245
+ raise TestError, 'catch me!'
246
+ end
247
+ assert_raises(TestError){record.update_attributes({})}
248
+ end
249
+
250
+ def test_exceptions_in_after_commit_on_update_are_not_swallowed
251
+ record = MockRecord.create
252
+ def record.do_after_update
253
+ raise TestError, 'catch me!'
254
+ end
255
+ assert_raises(TestError){record.update_attributes({})}
256
+ end
257
+
258
+ def test_exceptions_in_before_commit_on_destroy_are_not_swallowed
259
+ record = MockRecord.create
260
+ def record.do_before_destroy
261
+ raise TestError, 'catch me!'
262
+ end
263
+ assert_raises(TestError){record.destroy}
264
+ end
265
+
266
+ def test_exceptions_in_after_commit_on_destroy_are_not_swallowed
267
+ record = MockRecord.create
268
+ def record.do_after_destroy
269
+ raise TestError, 'catch me!'
270
+ end
271
+ assert_raises(TestError){record.destroy}
272
+ end
273
+
274
+ def test_transactions_in_hooks_do_not_cause_spurious_rollbacks
275
+ record = MockRecord.create
276
+ def record.do_after_destroy
277
+ MockRecord.transaction{}
278
+ raise TestError, 'catch me!'
279
+ end
280
+ assert_raises(TestError){record.destroy}
281
+ end
282
+ end
@@ -0,0 +1,59 @@
1
+ require '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,21 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
2
+ require 'test/unit'
3
+ require 'rubygems'
4
+ require 'active_record'
5
+ require 'sqlite3'
6
+
7
+ ActiveRecord::Base.establish_connection({"adapter" => "sqlite3", "database" => 'test.sqlite3'})
8
+ tables = %w( mock_records counting_records foos bars )
9
+
10
+ begin
11
+ tables.each do |table|
12
+ ActiveRecord::Base.connection.execute("drop table #{table}");
13
+ end
14
+ rescue
15
+ end
16
+
17
+ tables.each do |table|
18
+ ActiveRecord::Base.connection.execute("create table #{table}(id int, name string)");
19
+ end
20
+
21
+ require 'after_commit'
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aleksi-after_commit
3
+ version: !ruby/object:Gem::Version
4
+ hash: 7
5
+ prerelease: false
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 8
10
+ version: 1.0.8
11
+ platform: ruby
12
+ authors:
13
+ - Nick Muerdter
14
+ - David Yip
15
+ - Pat Allan
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+
20
+ date: 2010-08-05 00:00:00 +04:00
21
+ default_executable:
22
+ dependencies:
23
+ - !ruby/object:Gem::Dependency
24
+ name: activerecord
25
+ prerelease: false
26
+ requirement: &id001 !ruby/object:Gem::Requirement
27
+ none: false
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ hash: 3
32
+ segments:
33
+ - 0
34
+ version: "0"
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: sqlite3-ruby
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ 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 "
52
+ email: pat@freelancing-gods.com
53
+ executables: []
54
+
55
+ extensions: []
56
+
57
+ extra_rdoc_files:
58
+ - LICENSE
59
+ - README.textile
60
+ files:
61
+ - LICENSE
62
+ - lib/after_commit.rb
63
+ - lib/after_commit/active_record.rb
64
+ - lib/after_commit/active_support_callbacks.rb
65
+ - lib/after_commit/after_savepoint.rb
66
+ - lib/after_commit/connection_adapters.rb
67
+ - README.textile
68
+ - test/after_commit_test.rb
69
+ - test/observer_test.rb
70
+ - test/test_helper.rb
71
+ has_rdoc: true
72
+ homepage: http://github.com/freelancing-god/after_commit
73
+ licenses: []
74
+
75
+ post_install_message:
76
+ rdoc_options:
77
+ - --charset=UTF-8
78
+ require_paths:
79
+ - lib
80
+ required_ruby_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ hash: 3
86
+ segments:
87
+ - 0
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ hash: 3
95
+ segments:
96
+ - 0
97
+ version: "0"
98
+ requirements: []
99
+
100
+ rubyforge_project:
101
+ rubygems_version: 1.3.7
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: after_commit callback for ActiveRecord
105
+ test_files:
106
+ - test/after_commit_test.rb
107
+ - test/observer_test.rb
108
+ - test/test_helper.rb