after_commit_everywhere 1.1.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
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