aleksi-after_commit 1.0.8

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,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