after_commit 1.0.6 → 1.0.7
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/README.textile +12 -1
- data/lib/after_commit.rb +12 -1
- data/lib/after_commit/active_record.rb +38 -74
- data/lib/after_commit/active_support_callbacks.rb +59 -0
- data/lib/after_commit/after_savepoint.rb +86 -0
- data/lib/after_commit/connection_adapters.rb +53 -36
- data/test/after_commit_test.rb +190 -34
- data/test/test_helper.rb +10 -13
- metadata +27 -15
- data/lib/after_commit/test_bypass.rb +0 -30
data/README.textile
CHANGED
@@ -4,7 +4,7 @@ An ActiveRecord/Rails library to add @before_commit@, @after_commit@, @before_ro
|
|
4
4
|
|
5
5
|
h2. Installation
|
6
6
|
|
7
|
-
<pre><code>gem install after_commit
|
7
|
+
<pre><code>gem install after_commit</code></pre>
|
8
8
|
|
9
9
|
h2. Usage
|
10
10
|
|
@@ -35,6 +35,13 @@ You can use these just like you would any other callback:
|
|
35
35
|
end
|
36
36
|
end</code></pre>
|
37
37
|
|
38
|
+
h2. In Tests
|
39
|
+
|
40
|
+
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:
|
41
|
+
|
42
|
+
<pre><code>ActiveRecord::Base.send(:include, AfterCommit::AfterSavepoint)
|
43
|
+
ActiveRecord::Base.include_after_savepoint_extensions</code></pre>
|
44
|
+
|
38
45
|
h2. Credits
|
39
46
|
|
40
47
|
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.
|
@@ -49,3 +56,7 @@ This version (maintained by Pat Allan) includes the following patches:
|
|
49
56
|
* Scoping callbacks to specific connections ("Mat Brown":http://outofti.me/)
|
50
57
|
* before_* callbacks ("Trotter Cashion":http://trottercashion.com/)
|
51
58
|
* 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)
|
59
|
+
* Exception propagation ("George Ogata":http://github.com/oggy/)
|
60
|
+
* Keeping callbacks focused on the correct transactions ("Benjamin Stein":http://benjaminste.in/)
|
61
|
+
* Using savepoints for test helper ("Lars Klevan":http://www.linkedin.com/in/larsklevan and "Joel Chippindale":http://blog.monkeysthumb.org/)
|
62
|
+
* 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/)
|
data/lib/after_commit.rb
CHANGED
@@ -14,6 +14,11 @@ module AfterCommit
|
|
14
14
|
add_to_collection :committed_records_on_update, connection, record
|
15
15
|
end
|
16
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
|
+
|
17
22
|
def self.record_destroyed(connection, record)
|
18
23
|
prepare_collection :committed_records_on_destroy, connection
|
19
24
|
add_to_collection :committed_records_on_destroy, connection, record
|
@@ -31,6 +36,10 @@ module AfterCommit
|
|
31
36
|
collection :committed_records_on_update, connection
|
32
37
|
end
|
33
38
|
|
39
|
+
def self.saved_records(connection)
|
40
|
+
collection :committed_records_on_save, connection
|
41
|
+
end
|
42
|
+
|
34
43
|
def self.destroyed_records(connection)
|
35
44
|
collection :committed_records_on_destroy, connection
|
36
45
|
end
|
@@ -40,6 +49,7 @@ module AfterCommit
|
|
40
49
|
:committed_records,
|
41
50
|
:committed_records_on_create,
|
42
51
|
:committed_records_on_update,
|
52
|
+
:committed_records_on_save,
|
43
53
|
:committed_records_on_destroy
|
44
54
|
].each do |collection|
|
45
55
|
Thread.current[collection] ||= {}
|
@@ -62,9 +72,10 @@ module AfterCommit
|
|
62
72
|
end
|
63
73
|
end
|
64
74
|
|
75
|
+
require 'after_commit/active_support_callbacks'
|
65
76
|
require 'after_commit/active_record'
|
66
77
|
require 'after_commit/connection_adapters'
|
67
|
-
require 'after_commit/
|
78
|
+
require 'after_commit/after_savepoint'
|
68
79
|
|
69
80
|
ActiveRecord::Base.send(:include, AfterCommit::ActiveRecord)
|
70
81
|
ActiveRecord::Base.include_after_commit_extensions
|
@@ -31,91 +31,55 @@ module AfterCommit
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
else
|
48
|
-
class << self
|
49
|
-
# Handle after_commit callbacks - call all the registered callbacks.
|
50
|
-
def after_commit(*callbacks, &block)
|
51
|
-
callbacks << block if block_given?
|
52
|
-
write_inheritable_array(:after_commit, callbacks)
|
53
|
-
end
|
54
|
-
|
55
|
-
def after_commit_on_create(*callbacks, &block)
|
56
|
-
callbacks << block if block_given?
|
57
|
-
write_inheritable_array(:after_commit_on_create, callbacks)
|
58
|
-
end
|
59
|
-
|
60
|
-
def after_commit_on_update(*callbacks, &block)
|
61
|
-
callbacks << block if block_given?
|
62
|
-
write_inheritable_array(:after_commit_on_update, callbacks)
|
63
|
-
end
|
64
|
-
|
65
|
-
def after_commit_on_destroy(*callbacks, &block)
|
66
|
-
callbacks << block if block_given?
|
67
|
-
write_inheritable_array(:after_commit_on_destroy, callbacks)
|
68
|
-
end
|
69
|
-
|
70
|
-
def after_rollback(*callbacks, &block)
|
71
|
-
callbacks << block if block_given?
|
72
|
-
write_inheritable_array(:after_commit, callbacks)
|
73
|
-
end
|
74
|
-
|
75
|
-
def before_commit(*callbacks, &block)
|
76
|
-
callbacks << block if block_given?
|
77
|
-
write_inheritable_array(:before_commit, callbacks)
|
78
|
-
end
|
79
|
-
|
80
|
-
def before_commit_on_create(*callbacks, &block)
|
81
|
-
callbacks << block if block_given?
|
82
|
-
write_inheritable_array(:before_commit_on_create, callbacks)
|
83
|
-
end
|
84
|
-
|
85
|
-
def before_commit_on_update(*callbacks, &block)
|
86
|
-
callbacks << block if block_given?
|
87
|
-
write_inheritable_array(:before_commit_on_update, callbacks)
|
88
|
-
end
|
89
|
-
|
90
|
-
def before_commit_on_destroy(*callbacks, &block)
|
91
|
-
callbacks << block if block_given?
|
92
|
-
write_inheritable_array(:before_commit_on_destroy, callbacks)
|
93
|
-
end
|
94
|
-
|
95
|
-
def before_rollback(*callbacks, &block)
|
96
|
-
callbacks << block if block_given?
|
97
|
-
write_inheritable_array(:before_commit, callbacks)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
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
|
+
|
102
47
|
after_create :add_committed_record_on_create
|
103
48
|
after_update :add_committed_record_on_update
|
49
|
+
after_save :add_committed_record_on_save
|
104
50
|
after_destroy :add_committed_record_on_destroy
|
105
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
|
+
|
106
58
|
def add_committed_record_on_create
|
107
|
-
|
108
|
-
|
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
|
109
63
|
end
|
110
64
|
|
111
65
|
def add_committed_record_on_update
|
112
|
-
|
113
|
-
|
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
|
114
76
|
end
|
115
77
|
|
116
78
|
def add_committed_record_on_destroy
|
117
|
-
|
118
|
-
|
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
|
119
83
|
end
|
120
84
|
end
|
121
85
|
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,86 @@
|
|
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
|
+
def release_savepoint_with_callback
|
38
|
+
increment_transaction_pointer
|
39
|
+
committed = false
|
40
|
+
begin
|
41
|
+
trigger_before_commit_callbacks
|
42
|
+
trigger_before_commit_on_create_callbacks
|
43
|
+
trigger_before_commit_on_save_callbacks
|
44
|
+
trigger_before_commit_on_update_callbacks
|
45
|
+
trigger_before_commit_on_destroy_callbacks
|
46
|
+
|
47
|
+
release_savepoint_without_callback
|
48
|
+
committed = true
|
49
|
+
|
50
|
+
trigger_after_commit_callbacks
|
51
|
+
trigger_after_commit_on_create_callbacks
|
52
|
+
trigger_after_commit_on_save_callbacks
|
53
|
+
trigger_after_commit_on_update_callbacks
|
54
|
+
trigger_after_commit_on_destroy_callbacks
|
55
|
+
rescue
|
56
|
+
unless committed
|
57
|
+
decrement_transaction_pointer
|
58
|
+
rollback_to_savepoint
|
59
|
+
increment_transaction_pointer
|
60
|
+
end
|
61
|
+
ensure
|
62
|
+
AfterCommit.cleanup(self)
|
63
|
+
decrement_transaction_pointer
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias_method_chain :release_savepoint, :callback
|
67
|
+
|
68
|
+
# In the event the transaction fails and rolls back, nothing inside
|
69
|
+
# should recieve the after_commit callback, but do fire the after_rollback
|
70
|
+
# callback for each record that failed to be committed.
|
71
|
+
def rollback_to_savepoint_with_callback
|
72
|
+
increment_transaction_pointer
|
73
|
+
begin
|
74
|
+
trigger_before_rollback_callbacks
|
75
|
+
rollback_to_savepoint_without_callback
|
76
|
+
trigger_after_rollback_callbacks
|
77
|
+
ensure
|
78
|
+
AfterCommit.cleanup(self)
|
79
|
+
end
|
80
|
+
decrement_transaction_pointer
|
81
|
+
end
|
82
|
+
alias_method_chain :rollback_to_savepoint, :callback
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -2,6 +2,19 @@ module AfterCommit
|
|
2
2
|
module ConnectionAdapters
|
3
3
|
def self.included(base)
|
4
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
|
+
|
5
18
|
# The commit_db_transaction method gets called when the outermost
|
6
19
|
# transaction finishes and everything inside commits. We want to
|
7
20
|
# override it so that after this happens, any records that were saved
|
@@ -9,27 +22,36 @@ module AfterCommit
|
|
9
22
|
# callback fired.
|
10
23
|
def commit_db_transaction_with_callback
|
11
24
|
increment_transaction_pointer
|
12
|
-
committed = false
|
13
25
|
result = nil
|
14
26
|
begin
|
15
27
|
trigger_before_commit_callbacks
|
16
28
|
trigger_before_commit_on_create_callbacks
|
17
29
|
trigger_before_commit_on_update_callbacks
|
30
|
+
trigger_before_commit_on_save_callbacks
|
18
31
|
trigger_before_commit_on_destroy_callbacks
|
19
|
-
|
32
|
+
|
20
33
|
result = commit_db_transaction_without_callback
|
21
|
-
|
22
|
-
|
34
|
+
@disable_rollback = true
|
35
|
+
|
23
36
|
trigger_after_commit_callbacks
|
24
37
|
trigger_after_commit_on_create_callbacks
|
25
38
|
trigger_after_commit_on_update_callbacks
|
39
|
+
trigger_after_commit_on_save_callbacks
|
26
40
|
trigger_after_commit_on_destroy_callbacks
|
27
41
|
result
|
28
42
|
rescue
|
29
|
-
|
43
|
+
# Need to decrement the transaction pointer before calling
|
44
|
+
# rollback... to ensure it is not incremented twice
|
45
|
+
unless @disable_rollback
|
46
|
+
decrement_transaction_pointer
|
47
|
+
@already_decremented = true
|
48
|
+
end
|
49
|
+
|
50
|
+
# We still want to raise the exception.
|
51
|
+
raise
|
30
52
|
ensure
|
31
53
|
AfterCommit.cleanup(self)
|
32
|
-
decrement_transaction_pointer
|
54
|
+
decrement_transaction_pointer unless @already_decremented
|
33
55
|
end
|
34
56
|
end
|
35
57
|
alias_method_chain :commit_db_transaction, :callback
|
@@ -38,6 +60,8 @@ module AfterCommit
|
|
38
60
|
# should recieve the after_commit callback, but do fire the after_rollback
|
39
61
|
# callback for each record that failed to be committed.
|
40
62
|
def rollback_db_transaction_with_callback
|
63
|
+
return if @disable_rollback
|
64
|
+
increment_transaction_pointer
|
41
65
|
begin
|
42
66
|
result = nil
|
43
67
|
trigger_before_rollback_callbacks
|
@@ -46,7 +70,9 @@ module AfterCommit
|
|
46
70
|
result
|
47
71
|
ensure
|
48
72
|
AfterCommit.cleanup(self)
|
73
|
+
decrement_transaction_pointer
|
49
74
|
end
|
75
|
+
decrement_transaction_pointer
|
50
76
|
end
|
51
77
|
alias_method_chain :rollback_db_transaction, :callback
|
52
78
|
|
@@ -78,6 +104,12 @@ module AfterCommit
|
|
78
104
|
end
|
79
105
|
end
|
80
106
|
|
107
|
+
def trigger_before_commit_on_save_callbacks
|
108
|
+
AfterCommit.saved_records(self).each do |record|
|
109
|
+
record.send :callback, :before_commit_on_save
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
81
113
|
def trigger_before_commit_on_destroy_callbacks
|
82
114
|
AfterCommit.destroyed_records(self).each do |record|
|
83
115
|
record.send :callback, :before_commit_on_destroy
|
@@ -86,11 +118,7 @@ module AfterCommit
|
|
86
118
|
|
87
119
|
def trigger_before_rollback_callbacks
|
88
120
|
AfterCommit.records(self).each do |record|
|
89
|
-
|
90
|
-
record.send :callback, :before_rollback
|
91
|
-
rescue
|
92
|
-
#
|
93
|
-
end
|
121
|
+
record.send :callback, :before_rollback
|
94
122
|
end
|
95
123
|
end
|
96
124
|
|
@@ -98,23 +126,15 @@ module AfterCommit
|
|
98
126
|
# Trigger the after_commit callback for each of the committed
|
99
127
|
# records.
|
100
128
|
AfterCommit.records(self).each do |record|
|
101
|
-
|
102
|
-
record.send :callback, :after_commit
|
103
|
-
rescue
|
104
|
-
#
|
105
|
-
end
|
129
|
+
record.send :callback, :after_commit
|
106
130
|
end
|
107
131
|
end
|
108
|
-
|
132
|
+
|
109
133
|
def trigger_after_commit_on_create_callbacks
|
110
134
|
# Trigger the after_commit_on_create callback for each of the committed
|
111
135
|
# records.
|
112
136
|
AfterCommit.created_records(self).each do |record|
|
113
|
-
|
114
|
-
record.send :callback, :after_commit_on_create
|
115
|
-
rescue
|
116
|
-
#
|
117
|
-
end
|
137
|
+
record.send :callback, :after_commit_on_create
|
118
138
|
end
|
119
139
|
end
|
120
140
|
|
@@ -122,23 +142,23 @@ module AfterCommit
|
|
122
142
|
# Trigger the after_commit_on_update callback for each of the committed
|
123
143
|
# records.
|
124
144
|
AfterCommit.updated_records(self).each do |record|
|
125
|
-
|
126
|
-
record.send :callback, :after_commit_on_update
|
127
|
-
rescue
|
128
|
-
#
|
129
|
-
end
|
145
|
+
record.send :callback, :after_commit_on_update
|
130
146
|
end
|
131
147
|
end
|
132
148
|
|
149
|
+
def trigger_after_commit_on_save_callbacks
|
150
|
+
# Trigger the after_commit_on_save callback for each of the committed
|
151
|
+
# records.
|
152
|
+
AfterCommit.saved_records(self).each do |record|
|
153
|
+
record.send :callback, :after_commit_on_save
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
133
157
|
def trigger_after_commit_on_destroy_callbacks
|
134
158
|
# Trigger the after_commit_on_destroy callback for each of the committed
|
135
159
|
# records.
|
136
160
|
AfterCommit.destroyed_records(self).each do |record|
|
137
|
-
|
138
|
-
record.send :callback, :after_commit_on_destroy
|
139
|
-
rescue
|
140
|
-
#
|
141
|
-
end
|
161
|
+
record.send :callback, :after_commit_on_destroy
|
142
162
|
end
|
143
163
|
end
|
144
164
|
|
@@ -146,10 +166,7 @@ module AfterCommit
|
|
146
166
|
# Trigger the after_rollback callback for each of the committed
|
147
167
|
# records.
|
148
168
|
AfterCommit.records(self).each do |record|
|
149
|
-
|
150
|
-
record.send :callback, :after_rollback
|
151
|
-
rescue
|
152
|
-
end
|
169
|
+
record.send :callback, :after_rollback
|
153
170
|
end
|
154
171
|
end
|
155
172
|
|
data/test/after_commit_test.rb
CHANGED
@@ -1,41 +1,37 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
class MockRecord < ActiveRecord::Base
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
self.before_commit_on_destroy_called = true
|
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
|
24
23
|
end
|
24
|
+
|
25
|
+
end
|
25
26
|
|
27
|
+
class CountingRecord < ActiveRecord::Base
|
28
|
+
attr_accessor :after_commit_on_create_called
|
29
|
+
cattr_accessor :counter
|
30
|
+
@@counter=0
|
31
|
+
|
26
32
|
after_commit_on_create :do_after_create
|
27
33
|
def do_after_create
|
28
|
-
|
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
|
34
|
+
@@counter+=1
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
@@ -60,7 +56,7 @@ class Bar < ActiveRecord::Base
|
|
60
56
|
end
|
61
57
|
|
62
58
|
class UnsavableRecord < ActiveRecord::Base
|
63
|
-
attr_accessor :after_commit_called
|
59
|
+
attr_accessor :after_commit_called, :after_rollback_called
|
64
60
|
|
65
61
|
set_table_name 'mock_records'
|
66
62
|
|
@@ -75,10 +71,36 @@ class UnsavableRecord < ActiveRecord::Base
|
|
75
71
|
end
|
76
72
|
|
77
73
|
after_commit :after_commit
|
78
|
-
|
79
74
|
def after_commit
|
80
75
|
self.after_commit_called = true
|
81
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
|
82
104
|
end
|
83
105
|
|
84
106
|
class AfterCommitTest < Test::Unit::TestCase
|
@@ -88,10 +110,26 @@ class AfterCommitTest < Test::Unit::TestCase
|
|
88
110
|
|
89
111
|
def test_before_commit_on_update_is_called
|
90
112
|
record = MockRecord.create!
|
113
|
+
record.before_commit_on_update_called = false
|
91
114
|
record.save
|
92
115
|
assert_equal true, record.before_commit_on_update_called
|
93
116
|
end
|
94
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
|
+
|
95
133
|
def test_before_commit_on_destroy_is_called
|
96
134
|
assert_equal true, MockRecord.create!.destroy.before_commit_on_destroy_called
|
97
135
|
end
|
@@ -102,10 +140,26 @@ class AfterCommitTest < Test::Unit::TestCase
|
|
102
140
|
|
103
141
|
def test_after_commit_on_update_is_called
|
104
142
|
record = MockRecord.create!
|
143
|
+
record.after_commit_on_update_called = false
|
105
144
|
record.save
|
106
145
|
assert_equal true, record.after_commit_on_update_called
|
107
146
|
end
|
108
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
|
+
|
109
163
|
def test_after_commit_on_destroy_is_called
|
110
164
|
assert_equal true, MockRecord.create!.destroy.after_commit_on_destroy_called
|
111
165
|
end
|
@@ -113,14 +167,116 @@ class AfterCommitTest < Test::Unit::TestCase
|
|
113
167
|
def test_after_commit_does_not_trigger_when_transaction_rolls_back
|
114
168
|
record = UnsavableRecord.new
|
115
169
|
begin; record.save; rescue; end
|
116
|
-
|
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
|
+
|
117
176
|
assert_equal false, record.after_commit_called
|
118
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
|
119
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
|
+
|
120
199
|
def test_two_transactions_are_separate
|
121
200
|
Bar.delete_all
|
122
201
|
foo = Foo.create
|
123
202
|
|
124
203
|
assert_equal 1, foo.creating
|
125
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
|
126
282
|
end
|
data/test/test_helper.rb
CHANGED
@@ -2,23 +2,20 @@ $LOAD_PATH.unshift(File.dirname(__FILE__) + '/../lib')
|
|
2
2
|
require 'test/unit'
|
3
3
|
require 'rubygems'
|
4
4
|
require 'active_record'
|
5
|
-
|
6
|
-
begin
|
7
|
-
require 'sqlite3'
|
8
|
-
rescue LoadError
|
9
|
-
gem 'sqlite3-ruby'
|
10
|
-
retry
|
11
|
-
end
|
5
|
+
require 'sqlite3'
|
12
6
|
|
13
7
|
ActiveRecord::Base.establish_connection({"adapter" => "sqlite3", "database" => 'test.sqlite3'})
|
8
|
+
tables = %w( mock_records counting_records foos bars )
|
9
|
+
|
14
10
|
begin
|
15
|
-
|
16
|
-
|
17
|
-
|
11
|
+
tables.each do |table|
|
12
|
+
ActiveRecord::Base.connection.execute("drop table #{table}");
|
13
|
+
end
|
18
14
|
rescue
|
19
15
|
end
|
20
|
-
|
21
|
-
|
22
|
-
ActiveRecord::Base.connection.execute("create table
|
16
|
+
|
17
|
+
tables.each do |table|
|
18
|
+
ActiveRecord::Base.connection.execute("create table #{table}(id int, name string)");
|
19
|
+
end
|
23
20
|
|
24
21
|
require 'after_commit'
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: after_commit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 1
|
7
|
+
- 0
|
8
|
+
- 7
|
9
|
+
version: 1.0.7
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Nick Muerdter
|
@@ -11,29 +16,33 @@ autorequire:
|
|
11
16
|
bindir: bin
|
12
17
|
cert_chain: []
|
13
18
|
|
14
|
-
date: 2010-
|
19
|
+
date: 2010-05-20 00:00:00 +10:00
|
15
20
|
default_executable:
|
16
21
|
dependencies:
|
17
22
|
- !ruby/object:Gem::Dependency
|
18
23
|
name: activerecord
|
19
|
-
|
20
|
-
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
22
26
|
requirements:
|
23
27
|
- - ">="
|
24
28
|
- !ruby/object:Gem::Version
|
29
|
+
segments:
|
30
|
+
- 0
|
25
31
|
version: "0"
|
26
|
-
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
27
34
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
29
|
-
|
30
|
-
|
31
|
-
version_requirements: !ruby/object:Gem::Requirement
|
35
|
+
name: sqlite3-ruby
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
32
38
|
requirements:
|
33
39
|
- - ">="
|
34
40
|
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
35
43
|
version: "0"
|
36
|
-
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
37
46
|
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
47
|
email: pat@freelancing-gods.com
|
39
48
|
executables: []
|
@@ -47,8 +56,9 @@ files:
|
|
47
56
|
- LICENSE
|
48
57
|
- lib/after_commit.rb
|
49
58
|
- lib/after_commit/active_record.rb
|
59
|
+
- lib/after_commit/active_support_callbacks.rb
|
60
|
+
- lib/after_commit/after_savepoint.rb
|
50
61
|
- lib/after_commit/connection_adapters.rb
|
51
|
-
- lib/after_commit/test_bypass.rb
|
52
62
|
- README.textile
|
53
63
|
has_rdoc: true
|
54
64
|
homepage: http://github.com/freelancing-god/after_commit
|
@@ -63,18 +73,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
73
|
requirements:
|
64
74
|
- - ">="
|
65
75
|
- !ruby/object:Gem::Version
|
76
|
+
segments:
|
77
|
+
- 0
|
66
78
|
version: "0"
|
67
|
-
version:
|
68
79
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
80
|
requirements:
|
70
81
|
- - ">="
|
71
82
|
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
72
85
|
version: "0"
|
73
|
-
version:
|
74
86
|
requirements: []
|
75
87
|
|
76
88
|
rubyforge_project:
|
77
|
-
rubygems_version: 1.3.
|
89
|
+
rubygems_version: 1.3.6
|
78
90
|
signing_key:
|
79
91
|
specification_version: 3
|
80
92
|
summary: after_commit callback for ActiveRecord
|
@@ -1,30 +0,0 @@
|
|
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_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_on_create
|
17
|
-
callback :after_commit
|
18
|
-
callback :after_commit_on_create
|
19
|
-
end
|
20
|
-
|
21
|
-
def add_committed_record_on_update
|
22
|
-
callback :after_commit
|
23
|
-
callback :after_commit_on_update
|
24
|
-
end
|
25
|
-
|
26
|
-
def add_committed_record_on_destroy
|
27
|
-
callback :after_commit
|
28
|
-
callback :after_commit_on_destroy
|
29
|
-
end
|
30
|
-
end
|