after_commit_everywhere 1.1.0 → 1.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4425b11aa2651bd44ec24dc839e1338537f087f89c3f19f250d3ad2aeb0fad07
4
- data.tar.gz: 143406ceecd3d9d3e1f7999f8cbb3754c19186036a7b2f47f19996c932048e26
3
+ metadata.gz: f406689608dbc8a4a3c904dffd3f090ad6e284f037a4316728e7cadd5ccdb9ed
4
+ data.tar.gz: 9d70381c05b482fee5f7f1f705bd263c9e43bdfac2d7558dff2ac82e98c9201e
5
5
  SHA512:
6
- metadata.gz: fdaa287369af57e90b69699fdbd11b687491ea9c938af714e8a68871954b3685e07c4a9ba16939094ea6f082cec84ce351c9354341685f42bcd58bcc0307ee29
7
- data.tar.gz: 5a42c24b63cbdf2d84597e3fa6071d588a76e8821736239a146708dd155a8492dc657c29577022d7072c13ecdcc4d0b1ee22243701d1e1058ebcd13ee6072573
6
+ metadata.gz: 28bc55a3725c4baaca95574bf0b3631a411466dbcdee4e7c148c4d00c30f0ecd8a476863c65497a2c81df85978aa0705838376cc02f0a40fd4ff9f79d062db1c
7
+ data.tar.gz: 3013886554bb909368399f258a0047f55b87a4e9420eb2e923569b4c8cddfca438fb6ca2c470ae109f93fe668318efdf4cd59e785d858754f7df9df032f1b211
@@ -18,15 +18,6 @@ jobs:
18
18
  fail-fast: false
19
19
  matrix:
20
20
  include:
21
- - ruby: '2.5'
22
- activerecord: '4.2'
23
- gemfile: 'activerecord_4_2.gemfile'
24
- - ruby: '2.6'
25
- activerecord: '5.0'
26
- gemfile: 'activerecord_5_0.gemfile'
27
- - ruby: '2.6'
28
- activerecord: '5.1'
29
- gemfile: 'activerecord_5_1.gemfile'
30
21
  - ruby: '2.6'
31
22
  activerecord: '5.2'
32
23
  gemfile: 'activerecord_5_2.gemfile'
@@ -37,6 +28,9 @@ jobs:
37
28
  activerecord: '6.1'
38
29
  gemfile: 'activerecord_6_1.gemfile'
39
30
  - ruby: '3.0'
31
+ activerecord: '7.0'
32
+ gemfile: 'activerecord_7_0.gemfile'
33
+ - ruby: '3.1'
40
34
  activerecord: 'HEAD'
41
35
  gemfile: 'activerecord_master.gemfile'
42
36
  container:
data/Appraisals CHANGED
@@ -31,6 +31,12 @@ appraise "activerecord-6-1" do
31
31
  gem "rspec-rails", "~> 4.0"
32
32
  end
33
33
 
34
+ appraise "activerecord-7-0" do
35
+ gem "activerecord", "~> 7.0.0"
36
+ gem "sqlite3", "~> 1.4"
37
+ gem "rspec-rails", "~> 5.0"
38
+ end
39
+
34
40
  appraise "activerecord-master" do
35
41
  git "https://github.com/rails/rails.git" do
36
42
  gem "rails"
@@ -38,5 +44,5 @@ appraise "activerecord-master" do
38
44
  end
39
45
 
40
46
  gem "sqlite3", "~> 1.4"
41
- gem "rspec-rails", "~> 4.0"
47
+ gem "rspec-rails", "~> 5.0"
42
48
  end
data/CHANGELOG.md CHANGED
@@ -4,7 +4,65 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
- ## [Unreleased]
7
+ ## 1.3.0 (2022-10-28)
8
+
9
+ ### Added
10
+
11
+ - `in_transaction` helper method to execute code within existing transaction or start a new one if there is no tx open.
12
+
13
+ It is similar to `ActiveRecord::Base.transaction`, but it doesn't swallow `ActiveRecord::Rollback` exception in case when there is no transaction open.
14
+
15
+ See discussion at [#23](https://github.com/Envek/after_commit_everywhere/pull/23) for details.
16
+
17
+ [Pull request #23](https://github.com/Envek/after_commit_everywhere/pull/23) by [@jpcamara][].
18
+
19
+ - Ability to call `in_transaction` helper with the same arguments as [`ActiveRecord::Base.transaction`](https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-transaction). [@Envek][].
20
+
21
+ ## 1.2.2 (2022-06-20)
22
+
23
+ ### Fixed
24
+
25
+ - Connection leak from the connection pool when `after_commit` called outside Rails executor without connection checked out *and* some connections were already checked out from another threads.
26
+
27
+ See discussion at [issue #20](https://github.com/Envek/after_commit_everywhere/issues/20) for details.
28
+
29
+ [Pull request #22](https://github.com/Envek/after_commit_everywhere/pull/22) by [@Envek][].
30
+
31
+ ## 1.2.1 (2022-06-10)
32
+
33
+ ### Fixed
34
+
35
+ - Connection leak from the connection pool when `after_commit` called outside Rails executor without connection checked out
36
+
37
+ Usually all invocations of `after_commit` (whether it happens during serving HTTP request in Rails controller or performing job in Sidekiq worker process) are made inside [Rails executor](https://guides.rubyonrails.org/threading_and_code_execution.html#executor) which checks in any connections back to the connection pool that were checked out inside its block.
38
+
39
+ However, in cases when a) `after_commit` was called outside of Rails executor (3-rd party gems or non-Rails apps using ActiveRecord) **and** b) database connection hasn't been checked out yet, then connection will be checked out by `after_commit` implicitly by call to `ActiveRecord::Base.connection` and not checked in back afterwards causing it to _leak_ from the connection pool.
40
+
41
+ But in that case we can be sure that there is no transaction in progress ('cause one need to checkout connection and issue `BEGIN` to it), so we don't need to check it out at all and can fast-forward to `without_tx` action.
42
+
43
+ See discussion at [issue #20](https://github.com/Envek/after_commit_everywhere/issues/20) for details.
44
+
45
+ [Pull request #21](https://github.com/Envek/after_commit_everywhere/pull/21) by [@Envek][].
46
+
47
+ ## 1.2.0 (2022-03-26)
48
+
49
+ ### Added
50
+
51
+ - Allow to change callbacks' behavior when they are called outside transaction:
52
+
53
+ ```ruby
54
+ AfterCommitEverywhere.after_commit(without_tx: :raise) do
55
+ # Will be executed only if was called within transaction
56
+ # Error will be raised otherwise
57
+ end
58
+ ```
59
+
60
+ Available values for `without_tx` keyword argument:
61
+ - `:execute` to execute callback immediately
62
+ - `:warn_and_execute` to print warning and execute immediately
63
+ - `:raise` to raise an exception instead of executing
64
+
65
+ [Pull request #18](https://github.com/Envek/after_commit_everywhere/pull/18) by [@lolripgg][].
8
66
 
9
67
  ## 1.1.0 (2021-08-05)
10
68
 
@@ -54,3 +112,5 @@ See [#11](https://github.com/Envek/after_commit_everywhere/issues/11) for discus
54
112
  [@arjun810]: https://github.com/arjun810 "Arjun Singh"
55
113
  [@joevandyk]: https://github.com/joevandyk "Joe Van Dyk"
56
114
  [@stokarenko]: https://github.com/stokarenko "Sergey Tokarenko"
115
+ [@lolripgg]: https://github.com/lolripgg "James Brewer"
116
+ [@jpcamara]: https://github.com/jpcamara "JP Camara"
data/Gemfile.lock CHANGED
@@ -1,170 +1,185 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- after_commit_everywhere (1.0.0)
4
+ after_commit_everywhere (1.3.0)
5
5
  activerecord (>= 4.2)
6
6
  activesupport
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- actioncable (6.1.4)
12
- actionpack (= 6.1.4)
13
- activesupport (= 6.1.4)
11
+ actioncable (7.0.4)
12
+ actionpack (= 7.0.4)
13
+ activesupport (= 7.0.4)
14
14
  nio4r (~> 2.0)
15
15
  websocket-driver (>= 0.6.1)
16
- actionmailbox (6.1.4)
17
- actionpack (= 6.1.4)
18
- activejob (= 6.1.4)
19
- activerecord (= 6.1.4)
20
- activestorage (= 6.1.4)
21
- activesupport (= 6.1.4)
16
+ actionmailbox (7.0.4)
17
+ actionpack (= 7.0.4)
18
+ activejob (= 7.0.4)
19
+ activerecord (= 7.0.4)
20
+ activestorage (= 7.0.4)
21
+ activesupport (= 7.0.4)
22
22
  mail (>= 2.7.1)
23
- actionmailer (6.1.4)
24
- actionpack (= 6.1.4)
25
- actionview (= 6.1.4)
26
- activejob (= 6.1.4)
27
- activesupport (= 6.1.4)
23
+ net-imap
24
+ net-pop
25
+ net-smtp
26
+ actionmailer (7.0.4)
27
+ actionpack (= 7.0.4)
28
+ actionview (= 7.0.4)
29
+ activejob (= 7.0.4)
30
+ activesupport (= 7.0.4)
28
31
  mail (~> 2.5, >= 2.5.4)
32
+ net-imap
33
+ net-pop
34
+ net-smtp
29
35
  rails-dom-testing (~> 2.0)
30
- actionpack (6.1.4)
31
- actionview (= 6.1.4)
32
- activesupport (= 6.1.4)
33
- rack (~> 2.0, >= 2.0.9)
36
+ actionpack (7.0.4)
37
+ actionview (= 7.0.4)
38
+ activesupport (= 7.0.4)
39
+ rack (~> 2.0, >= 2.2.0)
34
40
  rack-test (>= 0.6.3)
35
41
  rails-dom-testing (~> 2.0)
36
42
  rails-html-sanitizer (~> 1.0, >= 1.2.0)
37
- actiontext (6.1.4)
38
- actionpack (= 6.1.4)
39
- activerecord (= 6.1.4)
40
- activestorage (= 6.1.4)
41
- activesupport (= 6.1.4)
43
+ actiontext (7.0.4)
44
+ actionpack (= 7.0.4)
45
+ activerecord (= 7.0.4)
46
+ activestorage (= 7.0.4)
47
+ activesupport (= 7.0.4)
48
+ globalid (>= 0.6.0)
42
49
  nokogiri (>= 1.8.5)
43
- actionview (6.1.4)
44
- activesupport (= 6.1.4)
50
+ actionview (7.0.4)
51
+ activesupport (= 7.0.4)
45
52
  builder (~> 3.1)
46
53
  erubi (~> 1.4)
47
54
  rails-dom-testing (~> 2.0)
48
55
  rails-html-sanitizer (~> 1.1, >= 1.2.0)
49
- active_attr (0.15.3)
50
- actionpack (>= 3.0.2, < 7.0)
51
- activemodel (>= 3.0.2, < 7.0)
52
- activesupport (>= 3.0.2, < 7.0)
53
- activejob (6.1.4)
54
- activesupport (= 6.1.4)
56
+ activejob (7.0.4)
57
+ activesupport (= 7.0.4)
55
58
  globalid (>= 0.3.6)
56
- activemodel (6.1.4)
57
- activesupport (= 6.1.4)
58
- activerecord (6.1.4)
59
- activemodel (= 6.1.4)
60
- activesupport (= 6.1.4)
61
- activestorage (6.1.4)
62
- actionpack (= 6.1.4)
63
- activejob (= 6.1.4)
64
- activerecord (= 6.1.4)
65
- activesupport (= 6.1.4)
66
- marcel (~> 1.0.0)
59
+ activemodel (7.0.4)
60
+ activesupport (= 7.0.4)
61
+ activerecord (7.0.4)
62
+ activemodel (= 7.0.4)
63
+ activesupport (= 7.0.4)
64
+ activestorage (7.0.4)
65
+ actionpack (= 7.0.4)
66
+ activejob (= 7.0.4)
67
+ activerecord (= 7.0.4)
68
+ activesupport (= 7.0.4)
69
+ marcel (~> 1.0)
67
70
  mini_mime (>= 1.1.0)
68
- activesupport (6.1.4)
71
+ activesupport (7.0.4)
69
72
  concurrent-ruby (~> 1.0, >= 1.0.2)
70
73
  i18n (>= 1.6, < 2)
71
74
  minitest (>= 5.1)
72
75
  tzinfo (~> 2.0)
73
- zeitwerk (~> 2.3)
74
- anyway_config (2.1.0)
75
- ruby-next-core (>= 0.11.0)
76
+ anyway_config (2.3.0)
77
+ ruby-next-core (>= 0.14.0)
76
78
  appraisal (2.4.1)
77
79
  bundler
78
80
  rake
79
81
  thor (>= 0.14.0)
80
82
  ast (2.4.2)
81
83
  builder (3.2.4)
84
+ byebug (11.1.3)
82
85
  coderay (1.1.3)
83
- concurrent-ruby (1.1.9)
86
+ concurrent-ruby (1.1.10)
84
87
  crass (1.0.6)
85
- diff-lcs (1.4.4)
86
- erubi (1.10.0)
87
- globalid (0.5.2)
88
+ diff-lcs (1.5.0)
89
+ dry-initializer (3.1.1)
90
+ erubi (1.11.0)
91
+ globalid (1.0.0)
88
92
  activesupport (>= 5.0)
89
- i18n (1.8.10)
93
+ i18n (1.12.0)
90
94
  concurrent-ruby (~> 1.0)
91
- isolator (0.7.0)
95
+ isolator (0.8.0)
92
96
  sniffer (>= 0.3.1)
93
97
  jaro_winkler (1.5.4)
94
- loofah (2.11.0)
98
+ loofah (2.19.0)
95
99
  crass (~> 1.0.2)
96
100
  nokogiri (>= 1.5.9)
97
101
  mail (2.7.1)
98
102
  mini_mime (>= 0.1.1)
99
- marcel (1.0.1)
103
+ marcel (1.0.2)
100
104
  method_source (1.0.0)
101
- mini_mime (1.1.0)
102
- mini_portile2 (2.6.1)
103
- minitest (5.14.4)
105
+ mini_mime (1.1.2)
106
+ mini_portile2 (2.8.0)
107
+ minitest (5.16.3)
108
+ net-imap (0.3.1)
109
+ net-protocol
110
+ net-pop (0.1.2)
111
+ net-protocol
112
+ net-protocol (0.1.3)
113
+ timeout
114
+ net-smtp (0.3.2)
115
+ net-protocol
104
116
  nio4r (2.5.8)
105
- nokogiri (1.12.2)
106
- mini_portile2 (~> 2.6.1)
117
+ nokogiri (1.13.9)
118
+ mini_portile2 (~> 2.8.0)
107
119
  racc (~> 1.4)
108
- parallel (1.20.1)
109
- parser (3.0.2.0)
120
+ parallel (1.22.1)
121
+ parser (3.1.2.1)
110
122
  ast (~> 2.4.1)
111
123
  pry (0.14.1)
112
124
  coderay (~> 1.1)
113
125
  method_source (~> 1.0)
114
- racc (1.5.2)
115
- rack (2.2.3)
116
- rack-test (1.1.0)
117
- rack (>= 1.0, < 3)
118
- rails (6.1.4)
119
- actioncable (= 6.1.4)
120
- actionmailbox (= 6.1.4)
121
- actionmailer (= 6.1.4)
122
- actionpack (= 6.1.4)
123
- actiontext (= 6.1.4)
124
- actionview (= 6.1.4)
125
- activejob (= 6.1.4)
126
- activemodel (= 6.1.4)
127
- activerecord (= 6.1.4)
128
- activestorage (= 6.1.4)
129
- activesupport (= 6.1.4)
126
+ pry-byebug (3.10.1)
127
+ byebug (~> 11.0)
128
+ pry (>= 0.13, < 0.15)
129
+ racc (1.6.0)
130
+ rack (2.2.3.1)
131
+ rack-test (2.0.2)
132
+ rack (>= 1.3)
133
+ rails (7.0.4)
134
+ actioncable (= 7.0.4)
135
+ actionmailbox (= 7.0.4)
136
+ actionmailer (= 7.0.4)
137
+ actionpack (= 7.0.4)
138
+ actiontext (= 7.0.4)
139
+ actionview (= 7.0.4)
140
+ activejob (= 7.0.4)
141
+ activemodel (= 7.0.4)
142
+ activerecord (= 7.0.4)
143
+ activestorage (= 7.0.4)
144
+ activesupport (= 7.0.4)
130
145
  bundler (>= 1.15.0)
131
- railties (= 6.1.4)
132
- sprockets-rails (>= 2.0.0)
146
+ railties (= 7.0.4)
133
147
  rails-dom-testing (2.0.3)
134
148
  activesupport (>= 4.2.0)
135
149
  nokogiri (>= 1.6)
136
- rails-html-sanitizer (1.3.0)
150
+ rails-html-sanitizer (1.4.3)
137
151
  loofah (~> 2.3)
138
- railties (6.1.4)
139
- actionpack (= 6.1.4)
140
- activesupport (= 6.1.4)
152
+ railties (7.0.4)
153
+ actionpack (= 7.0.4)
154
+ activesupport (= 7.0.4)
141
155
  method_source
142
- rake (>= 0.13)
156
+ rake (>= 12.2)
143
157
  thor (~> 1.0)
144
- rainbow (3.0.0)
158
+ zeitwerk (~> 2.5)
159
+ rainbow (3.1.1)
145
160
  rake (13.0.6)
146
161
  rexml (3.2.5)
147
- rspec (3.10.0)
148
- rspec-core (~> 3.10.0)
149
- rspec-expectations (~> 3.10.0)
150
- rspec-mocks (~> 3.10.0)
151
- rspec-core (3.10.1)
152
- rspec-support (~> 3.10.0)
153
- rspec-expectations (3.10.1)
162
+ rspec (3.12.0)
163
+ rspec-core (~> 3.12.0)
164
+ rspec-expectations (~> 3.12.0)
165
+ rspec-mocks (~> 3.12.0)
166
+ rspec-core (3.12.0)
167
+ rspec-support (~> 3.12.0)
168
+ rspec-expectations (3.12.0)
154
169
  diff-lcs (>= 1.2.0, < 2.0)
155
- rspec-support (~> 3.10.0)
156
- rspec-mocks (3.10.2)
170
+ rspec-support (~> 3.12.0)
171
+ rspec-mocks (3.12.0)
157
172
  diff-lcs (>= 1.2.0, < 2.0)
158
- rspec-support (~> 3.10.0)
159
- rspec-rails (5.0.1)
160
- actionpack (>= 5.2)
161
- activesupport (>= 5.2)
162
- railties (>= 5.2)
163
- rspec-core (~> 3.10)
164
- rspec-expectations (~> 3.10)
165
- rspec-mocks (~> 3.10)
166
- rspec-support (~> 3.10)
167
- rspec-support (3.10.2)
173
+ rspec-support (~> 3.12.0)
174
+ rspec-rails (6.0.1)
175
+ actionpack (>= 6.1)
176
+ activesupport (>= 6.1)
177
+ railties (>= 6.1)
178
+ rspec-core (~> 3.11)
179
+ rspec-expectations (~> 3.11)
180
+ rspec-mocks (~> 3.11)
181
+ rspec-support (~> 3.11)
182
+ rspec-support (3.12.0)
168
183
  rubocop (0.81.0)
169
184
  jaro_winkler (~> 1.5.1)
170
185
  parallel (~> 1.10)
@@ -173,27 +188,25 @@ GEM
173
188
  rexml
174
189
  ruby-progressbar (~> 1.7)
175
190
  unicode-display_width (>= 1.4.0, < 2.0)
176
- ruby-next-core (0.12.0)
191
+ ruby-next-core (0.15.3)
177
192
  ruby-progressbar (1.11.0)
178
- sniffer (0.4.0)
179
- active_attr (>= 0.10.2)
193
+ sniffer (0.5.0)
180
194
  anyway_config (>= 1.0)
181
- sprockets (4.0.2)
195
+ dry-initializer (~> 3)
196
+ sqlite3 (1.5.3)
197
+ mini_portile2 (~> 2.8.0)
198
+ thor (1.2.1)
199
+ timeout (0.3.0)
200
+ tzinfo (2.0.5)
182
201
  concurrent-ruby (~> 1.0)
183
- rack (> 1, < 3)
184
- sprockets-rails (3.2.2)
185
- actionpack (>= 4.0)
186
- activesupport (>= 4.0)
187
- sprockets (>= 3.0.0)
188
- sqlite3 (1.4.2)
189
- thor (1.1.0)
190
- tzinfo (2.0.4)
191
- concurrent-ruby (~> 1.0)
192
- unicode-display_width (1.7.0)
202
+ unicode-display_width (1.8.0)
203
+ webrick (1.7.0)
193
204
  websocket-driver (0.7.5)
194
205
  websocket-extensions (>= 0.1.0)
195
206
  websocket-extensions (0.1.5)
196
- zeitwerk (2.4.2)
207
+ yard (0.9.28)
208
+ webrick (~> 1.7.0)
209
+ zeitwerk (2.6.1)
197
210
 
198
211
  PLATFORMS
199
212
  ruby
@@ -204,12 +217,14 @@ DEPENDENCIES
204
217
  bundler (~> 2.0)
205
218
  isolator (~> 0.7)
206
219
  pry
220
+ pry-byebug
207
221
  rails
208
222
  rake (~> 13.0)
209
223
  rspec (~> 3.0)
210
224
  rspec-rails
211
225
  rubocop (~> 0.81.0)
212
226
  sqlite3 (~> 1.3, >= 1.3.6)
227
+ yard
213
228
 
214
229
  BUNDLED WITH
215
- 2.2.25
230
+ 2.3.16
data/README.md CHANGED
@@ -111,13 +111,89 @@ Will be executed right after transaction in which it have been declared was roll
111
111
 
112
112
  If called outside transaction will raise an exception!
113
113
 
114
- Please keep in mind ActiveRecord's [limitations for rolling back nested transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#module-ActiveRecord::Transactions::ClassMethods-label-Nested+transactions).
114
+ Please keep in mind ActiveRecord's [limitations for rolling back nested transactions](http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#module-ActiveRecord::Transactions::ClassMethods-label-Nested+transactions). See [`in_transaction`](#in_transaction) for a workaround to this limitation.
115
115
 
116
116
  ### Available helper methods
117
117
 
118
+ #### `in_transaction`
119
+
120
+ Makes sure the provided block is running in a transaction.
121
+
122
+ This method aims to provide clearer intention than a typical `ActiveRecord::Base.transaction` block - `in_transaction` only cares that _some_ transaction is present, not that a transaction is nested in any way.
123
+
124
+ If a transaction is present, it will yield without taking any action. Note that this means `ActiveRecord::Rollback` errors will not be trapped by `in_transaction` but will propagate up to the nearest parent transaction block.
125
+
126
+ If no transaction is present, the provided block will open a new transaction.
127
+
128
+ ```rb
129
+ class ServiceObjectBtw
130
+ include AfterCommitEverywhere
131
+
132
+ def call
133
+ in_transaction do
134
+ an_update
135
+ another_update
136
+ after_commit { puts "We're all done!" }
137
+ end
138
+ end
139
+ end
140
+ ```
141
+
142
+ Our service object can run its database operations safely when run in isolation.
143
+
144
+ ```rb
145
+ ServiceObjectBtw.new.call # This opens a new #transaction block
146
+ ```
147
+
148
+ If it is later called from code already wrapped in a transaction, the existing transaction will be utilized without any nesting:
149
+
150
+ ```rb
151
+ ActiveRecord::Base.transaction do
152
+ new_update
153
+ next_update
154
+ # This no longer opens a new #transaction block, because one is already present
155
+ ServiceObjectBtw.new.call
156
+ end
157
+ ```
158
+
159
+ This can be called directly on the module as well:
160
+
161
+ ```rb
162
+ AfterCommitEverywhere.in_transaction do
163
+ AfterCommitEverywhere.after_commit { puts "We're all done!" }
164
+ end
165
+ ```
166
+
118
167
  #### `in_transaction?`
119
168
 
120
- Returns `true` when called inside open transaction, `false` otherwise.
169
+ Returns `true` when called inside an open transaction, `false` otherwise.
170
+
171
+ ```rb
172
+ def check_for_transaction
173
+ if in_transaction?
174
+ puts "We're in a transaction!"
175
+ else
176
+ puts "We're not in a transaction..."
177
+ end
178
+ end
179
+
180
+ check_for_transaction
181
+ # => prints "We're not in a transaction..."
182
+
183
+ in_transaction do
184
+ check_for_transaction
185
+ end
186
+ # => prints "We're in a transaction!"
187
+ ```
188
+
189
+ ### Available callback options
190
+
191
+ - `without_tx` allows to change default callback behavior if called without transaction open.
192
+
193
+ Available values:
194
+ - `:execute` to execute callback immediately
195
+ - `:warn_and_execute` to print warning and execute immediately
196
+ - `:raise` to raise an exception instead of executing
121
197
 
122
198
  ### FAQ
123
199
 
@@ -171,7 +247,7 @@ class Post < ActiveRecord::Base
171
247
  end
172
248
  ```
173
249
 
174
- However, if you do something in models that requires defining such ad-hoc transactional callbacks, it may indicate that your models have too many responsibilities and these methods should be extracted to separate secialized layers (service objects, etc).
250
+ However, if you do something in models that requires defining such ad-hoc transactional callbacks, it may indicate that your models have too many responsibilities and these methods should be extracted to separate specialized layers (service objects, etc).
175
251
 
176
252
  ## Development
177
253
 
@@ -34,10 +34,12 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency "bundler", "~> 2.0"
35
35
  spec.add_development_dependency "isolator", "~> 0.7"
36
36
  spec.add_development_dependency "pry"
37
+ spec.add_development_dependency "pry-byebug"
37
38
  spec.add_development_dependency "rails"
38
39
  spec.add_development_dependency "rake", "~> 13.0"
39
40
  spec.add_development_dependency "rspec", "~> 3.0"
40
41
  spec.add_development_dependency "rspec-rails"
41
42
  spec.add_development_dependency "rubocop", "~> 0.81.0"
42
43
  spec.add_development_dependency "sqlite3", "~> 1.3", ">= 1.3.6"
44
+ spec.add_development_dependency "yard"
43
45
  end
@@ -0,0 +1,9 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "activerecord", "~> 7.0.0"
6
+ gem "sqlite3", "~> 1.4"
7
+ gem "rspec-rails", "~> 5.0"
8
+
9
+ gemspec path: "../"
@@ -8,6 +8,6 @@ git "https://github.com/rails/rails.git" do
8
8
  end
9
9
 
10
10
  gem "sqlite3", "~> 1.4"
11
- gem "rspec-rails", "~> 4.0"
11
+ gem "rspec-rails", "~> 5.0"
12
12
 
13
13
  gemspec path: "../"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AfterCommitEverywhere
4
- VERSION = "1.1.0"
4
+ VERSION = "1.3.0"
5
5
  end
@@ -14,36 +14,60 @@ module AfterCommitEverywhere
14
14
  class NotInTransaction < RuntimeError; end
15
15
 
16
16
  delegate :after_commit, :before_commit, :after_rollback, to: AfterCommitEverywhere
17
- delegate :in_transaction?, to: AfterCommitEverywhere
17
+ delegate :in_transaction?, :in_transaction, to: AfterCommitEverywhere
18
+
19
+ # Causes {before_commit} and {after_commit} to raise an exception when
20
+ # called outside a transaction.
21
+ RAISE = :raise
22
+ # Causes {before_commit} and {after_commit} to execute the given callback
23
+ # immediately when called outside a transaction.
24
+ EXECUTE = :execute
25
+ # Causes {before_commit} and {after_commit} to log a warning before calling
26
+ # the given callback immediately when called outside a transaction.
27
+ WARN_AND_EXECUTE = :warn_and_execute
18
28
 
19
29
  class << self
20
30
  # Runs +callback+ after successful commit of outermost transaction for
21
31
  # database +connection+.
22
32
  #
23
- # If called outside transaction it will execute callback immediately.
33
+ # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection to operate in. Defaults to +ActiveRecord::Base.connection+
34
+ # @param without_tx [Symbol] Determines the behavior of this function when
35
+ # called without an open transaction.
36
+ #
37
+ # Must be one of: {RAISE}, {EXECUTE}, or {WARN_AND_EXECUTE}.
24
38
  #
25
- # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter]
26
39
  # @param callback [#call] Callback to be executed
27
40
  # @return void
28
- def after_commit(connection: ActiveRecord::Base.connection, &callback)
41
+ def after_commit(
42
+ connection: nil,
43
+ without_tx: EXECUTE,
44
+ &callback
45
+ )
29
46
  register_callback(
30
47
  connection: connection,
31
48
  name: __method__,
32
49
  callback: callback,
33
- no_tx_action: :execute,
50
+ without_tx: without_tx,
34
51
  )
35
52
  end
36
53
 
37
54
  # Runs +callback+ before committing of outermost transaction for +connection+.
38
55
  #
39
- # If called outside transaction it will execute callback immediately.
40
- #
41
56
  # Available only since Ruby on Rails 5.0. See https://github.com/rails/rails/pull/18936
42
57
  #
43
- # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter]
58
+ # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection to operate in. Defaults to +ActiveRecord::Base.connection+
59
+ # @param without_tx [Symbol] Determines the behavior of this function when
60
+ # called without an open transaction.
61
+ #
62
+ # Must be one of: {RAISE}, {EXECUTE}, or {WARN_AND_EXECUTE}.
63
+ #
44
64
  # @param callback [#call] Callback to be executed
45
65
  # @return void
46
- def before_commit(connection: ActiveRecord::Base.connection, &callback)
66
+ def before_commit(
67
+ connection: nil,
68
+ without_tx: WARN_AND_EXECUTE,
69
+ &callback
70
+ )
47
71
  if ActiveRecord::VERSION::MAJOR < 5
48
72
  raise NotImplementedError, "#{__method__} works only with Rails 5.0+"
49
73
  end
@@ -52,7 +76,7 @@ module AfterCommitEverywhere
52
76
  connection: connection,
53
77
  name: __method__,
54
78
  callback: callback,
55
- no_tx_action: :warn_and_execute,
79
+ without_tx: without_tx,
56
80
  )
57
81
  end
58
82
 
@@ -62,42 +86,76 @@ module AfterCommitEverywhere
62
86
  # Caveat: do not raise +ActivRecord::Rollback+ in nested transaction block!
63
87
  # See http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html#module-ActiveRecord::Transactions::ClassMethods-label-Nested+transactions
64
88
  #
65
- # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter]
89
+ # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection to operate in. Defaults to +ActiveRecord::Base.connection+
66
90
  # @param callback [#call] Callback to be executed
67
91
  # @return void
68
92
  # @raise [NotInTransaction] if called outside transaction.
69
- def after_rollback(connection: ActiveRecord::Base.connection, &callback)
93
+ def after_rollback(connection: nil, &callback)
70
94
  register_callback(
71
95
  connection: connection,
72
96
  name: __method__,
73
97
  callback: callback,
74
- no_tx_action: :exception,
98
+ without_tx: RAISE,
75
99
  )
76
100
  end
77
101
 
78
102
  # @api private
79
- def register_callback(connection:, name:, no_tx_action:, callback:)
103
+ def register_callback(connection: nil, name:, without_tx:, callback:)
80
104
  raise ArgumentError, "Provide callback to #{name}" unless callback
81
105
 
82
106
  unless in_transaction?(connection)
83
- case no_tx_action
84
- when :warn_and_execute
107
+ case without_tx
108
+ when WARN_AND_EXECUTE
85
109
  warn "#{name}: No transaction open. Executing callback immediately."
86
110
  return callback.call
87
- when :execute
111
+ when EXECUTE
88
112
  return callback.call
89
- when :exception
113
+ when RAISE
90
114
  raise NotInTransaction, "#{name} is useless outside transaction"
115
+ else
116
+ raise ArgumentError, "Invalid \"without_tx\": \"#{without_tx}\""
91
117
  end
92
118
  end
119
+
120
+ connection ||= default_connection
93
121
  wrap = Wrap.new(connection: connection, "#{name}": callback)
94
122
  connection.add_transaction_record(wrap)
95
123
  end
96
124
 
97
125
  # Helper method to determine whether we're currently in transaction or not
98
- def in_transaction?(connection = ActiveRecord::Base.connection)
126
+ def in_transaction?(connection = nil)
127
+ # Don't establish new connection if not connected: we apparently not in transaction
128
+ return false unless connection || ActiveRecord::Base.connection_pool.active_connection?
129
+
130
+ connection ||= default_connection
99
131
  # service transactions (tests and database_cleaner) are not joinable
100
132
  connection.transaction_open? && connection.current_transaction.joinable?
101
133
  end
134
+
135
+ # Makes sure the provided block runs in a transaction. If we are not currently in a transaction, a new transaction is started.
136
+ #
137
+ # It mimics the ActiveRecord's +transaction+ method's API and actually uses it under the hood.
138
+ #
139
+ # However, the main difference is that it doesn't swallow +ActiveRecord::Rollback+ exception in case when there is no transaction open.
140
+ #
141
+ # @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-transaction
142
+ #
143
+ # @param connection [ActiveRecord::ConnectionAdapters::AbstractAdapter] Database connection to operate in. Defaults to +ActiveRecord::Base.connection+
144
+ # @param requires_new [Boolean] Forces creation of new subtransaction (savepoint) even if transaction is already opened.
145
+ # @param new_tx_options [Hash<Symbol, void>] Options to be passed to +connection.transaction+ on new transaction creation
146
+ # @return void
147
+ def in_transaction(connection = default_connection, requires_new: false, **new_tx_options)
148
+ if in_transaction?(connection) && !requires_new
149
+ yield
150
+ else
151
+ connection.transaction(requires_new: requires_new, **new_tx_options) { yield }
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def default_connection
158
+ ActiveRecord::Base.connection
159
+ end
102
160
  end
103
161
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: after_commit_everywhere
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrey Novikov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-05 00:00:00.000000000 Z
11
+ date: 2022-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -94,6 +94,20 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: pry-byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
97
111
  - !ruby/object:Gem::Dependency
98
112
  name: rails
99
113
  requirement: !ruby/object:Gem::Requirement
@@ -184,6 +198,20 @@ dependencies:
184
198
  - - ">="
185
199
  - !ruby/object:Gem::Version
186
200
  version: 1.3.6
201
+ - !ruby/object:Gem::Dependency
202
+ name: yard
203
+ requirement: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ type: :development
209
+ prerelease: false
210
+ version_requirements: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
187
215
  description: Brings before_commit, after_commit, and after_rollback transactional
188
216
  callbacks outside of your ActiveRecord models.
189
217
  email:
@@ -214,6 +242,7 @@ files:
214
242
  - gemfiles/activerecord_5_2.gemfile
215
243
  - gemfiles/activerecord_6_0.gemfile
216
244
  - gemfiles/activerecord_6_1.gemfile
245
+ - gemfiles/activerecord_7_0.gemfile
217
246
  - gemfiles/activerecord_master.gemfile
218
247
  - lib/after_commit_everywhere.rb
219
248
  - lib/after_commit_everywhere/version.rb