trx_ext 2.0.0 → 3.0.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 +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +38 -160
- data/bin/test_all_ar_versions +1 -1
- data/lib/trx_ext/version.rb +1 -1
- data/lib/trx_ext.rb +1 -3
- data/trx_ext.gemspec +2 -2
- metadata +13 -9
- data/lib/trx_ext/callback_pool.rb +0 -108
- data/lib/trx_ext/transaction.rb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 437621539108dd313b7d2b91383ca64009bc64472cb71a67e851f1eb76b3a894
|
4
|
+
data.tar.gz: 6b18b50ebdd5ac8b91fc7f5fd870cdc30b2d8a421a5ba7c2d3a42620b9460218
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: de55ddb63c774c507bcb2c88e13f55f34d97fa4c16576eb77c12b8dc27ab61a70f00790715a60ddc7b5338ad6db40733e66878264e535389dd210ab8da172b9c
|
7
|
+
data.tar.gz: ed336131965bca0cee5818f39d17e025cd89da876fbbcb5084d2f7028fb00bf39a1297e2df88196eb9b130ede88bd312b480ccf2119c94277880aeef0030348c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [3.0.0] - 2024-09-20
|
4
|
+
- Drop `trx_ext` callbacks implementation in favour of rails v7.2 transaction callbacks
|
5
|
+
|
6
|
+
## [2.0.1] - 2024-09-20
|
7
|
+
- Restrict rails version to `< 7.2`. Rails v7.2 has suddenly released its own transaction callbacks which interferes with `trx_ext` transaction callbacks. Example of rails v7.2 transaction callbacks:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
Article.transaction do |transaction|
|
11
|
+
article.update(published: true)
|
12
|
+
transaction.after_commit do
|
13
|
+
PublishNotificationMailer.with(article: article).deliver_later
|
14
|
+
end
|
15
|
+
end
|
16
|
+
```
|
17
|
+
|
18
|
+
Next major version of `trx_ext` will drop its own transaction callbacks implementation and will start relying on rails' transaction callbacks.
|
19
|
+
|
3
20
|
## [2.0.0] - 2024-04-14
|
4
21
|
|
5
22
|
- `trx_ext` now supports any adapter(except `mysql`; `mysql2` is supported though), supported by rails
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# TrxExt
|
2
2
|
|
3
|
-
Extends functionality of ActiveRecord's transaction to auto-retry failed SQL transaction in case of deadlock, serialization error or unique constraint error.
|
3
|
+
Extends functionality of ActiveRecord's transaction to auto-retry failed SQL transaction in case of deadlock, serialization error or unique constraint error. The implementation is not bound to any database, but relies on the rails connection adapters instead. Thus, if your database is supported by rails out of the box, then the gem's features will just work. Currently supported adapters:
|
4
4
|
|
5
5
|
- `postgresql`
|
6
6
|
- `mysql2`
|
@@ -9,14 +9,15 @@ Extends functionality of ActiveRecord's transaction to auto-retry failed SQL tra
|
|
9
9
|
|
10
10
|
**WARNING!**
|
11
11
|
|
12
|
-
Because the implementation of this gem
|
13
|
-
|
14
|
-
Currently, the implementation is tested for rails v7.1+. **If you need the support of rails v6.0, v6.1, v7.0 - please use v1.x of this gem, but it works with PostgreSQL only.**
|
12
|
+
Because the implementation of this gem wraps some ActiveRecord methods - carefully test its integration into your project. For example, if your application patches ActiveRecord or if some of your gems patches ActiveRecord - there might be conflicts in the implementation which could potentially lead to the data loss.
|
15
13
|
|
16
14
|
## Requirements
|
17
15
|
|
18
|
-
- ActiveRecord 7.
|
19
|
-
- Ruby 3
|
16
|
+
- ActiveRecord 7.2+
|
17
|
+
- Ruby 3.1+
|
18
|
+
|
19
|
+
**If you need the support of rails v6.0, v6.1, v7.0 - please use v1.x of this gem, but it works with PostgreSQL only.**
|
20
|
+
**If you need the support of rails v7.1 - please use v2.x of this gem.**
|
20
21
|
|
21
22
|
## Installation
|
22
23
|
|
@@ -46,15 +47,15 @@ end
|
|
46
47
|
|
47
48
|
trx do
|
48
49
|
DummyRecord.first || DummyRecord.create
|
49
|
-
trx do |
|
50
|
-
|
50
|
+
trx do |t|
|
51
|
+
t.after_commit { puts "This message will be printed after COMMIT statement." }
|
51
52
|
end
|
52
53
|
end
|
53
54
|
|
54
55
|
trx do
|
55
56
|
DummyRecord.first || DummyRecord.create
|
56
|
-
trx do |
|
57
|
-
|
57
|
+
trx do |t|
|
58
|
+
t.after_rollback { puts "This message will be printed after ROLLBACK statement." }
|
58
59
|
end
|
59
60
|
raise ActiveRecord::Rollback
|
60
61
|
end
|
@@ -62,7 +63,7 @@ end
|
|
62
63
|
class DummyRecord
|
63
64
|
# Wrap method in transaction
|
64
65
|
wrap_in_trx def some_method_with_quieries
|
65
|
-
DummyRecord.first || DummyRecord.create
|
66
|
+
DummyRecord.first || DummyRecord.create
|
66
67
|
end
|
67
68
|
end
|
68
69
|
```
|
@@ -82,7 +83,7 @@ If you want to wrap some method into a transaction using `wrap_in_trx` outside t
|
|
82
83
|
class MyAwesomeLib
|
83
84
|
# Wrap method in transaction
|
84
85
|
def some_method_with_quieries
|
85
|
-
DummyRecord.first || DummyRecord.create
|
86
|
+
DummyRecord.first || DummyRecord.create
|
86
87
|
end
|
87
88
|
wrap_in_trx :some_method_with_quieries, 'DummyRecord'
|
88
89
|
end
|
@@ -91,9 +92,9 @@ end
|
|
91
92
|
## Configuration
|
92
93
|
|
93
94
|
```ruby
|
94
|
-
TrxExt.configure do |
|
95
|
+
TrxExt.configure do |config|
|
95
96
|
# Number of retries before failing when unique constraint error raises. Default is 5
|
96
|
-
|
97
|
+
config.unique_retries = 5
|
97
98
|
end
|
98
99
|
```
|
99
100
|
|
@@ -194,7 +195,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
194
195
|
|
195
196
|
def update_posts
|
196
197
|
trx do
|
197
|
-
@user.posts.update_all(banned: true) if @user.user_permission.admin?
|
198
|
+
@user.posts.update_all(banned: true) if @user.user_permission.admin?
|
198
199
|
end
|
199
200
|
end
|
200
201
|
```
|
@@ -230,17 +231,17 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
230
231
|
COMMIT
|
231
232
|
```
|
232
233
|
|
233
|
-
* It may happen that you need to invoke mailer's method inside `trx` block and pass there values that are calculated within the transaction block. Normally, you need to extract those values into after-transaction code and invoke mailer after transaction's end. Use `
|
234
|
+
* It may happen that you need to invoke mailer's method inside `trx` block and pass there values that are calculated within the transaction block. Normally, you need to extract those values into after-transaction code and invoke mailer after transaction's end. Use `after_commit` callback to simplify your code:
|
234
235
|
|
235
236
|
#### Bad
|
236
237
|
```ruby
|
237
238
|
trx do
|
238
239
|
user = User.find_or_initialize_by(email: email)
|
239
240
|
if user.save
|
240
|
-
# May be invoked more than one time if transaction is retried
|
241
|
+
# May be invoked more than one time if transaction is retried
|
241
242
|
Mailer.registration_confirmation(user.id).deliver_later
|
242
243
|
end
|
243
|
-
end
|
244
|
+
end
|
244
245
|
```
|
245
246
|
|
246
247
|
#### Good (before refactoring)
|
@@ -256,10 +257,10 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
256
257
|
|
257
258
|
#### Good (after refactoring)
|
258
259
|
```ruby
|
259
|
-
trx do |
|
260
|
+
trx do |t|
|
260
261
|
user = User.find_or_initialize_by(email: email)
|
261
262
|
if user.save
|
262
|
-
|
263
|
+
t.after_commit { Mailer.registration_confirmation(user.id).deliver_later }
|
263
264
|
end
|
264
265
|
end
|
265
266
|
```
|
@@ -273,11 +274,11 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
273
274
|
User.deleted.find_each do |user|
|
274
275
|
if user.created_at > 2.days.ago
|
275
276
|
user.active!
|
276
|
-
resurrected_users_count += 1
|
277
|
+
resurrected_users_count += 1
|
277
278
|
end
|
278
279
|
end
|
279
280
|
end
|
280
|
-
puts resurrected_users_count
|
281
|
+
puts resurrected_users_count
|
281
282
|
```
|
282
283
|
|
283
284
|
#### Good
|
@@ -288,7 +289,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
288
289
|
User.deleted.find_each do |user|
|
289
290
|
if user.created_at > 2.days.ago
|
290
291
|
user.active!
|
291
|
-
resurrected_users_count += 1
|
292
|
+
resurrected_users_count += 1
|
292
293
|
end
|
293
294
|
end
|
294
295
|
end
|
@@ -304,7 +305,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
304
305
|
if @user.update(user_params)
|
305
306
|
redirect_to @user
|
306
307
|
else
|
307
|
-
render :edit
|
308
|
+
render :edit
|
308
309
|
end
|
309
310
|
end
|
310
311
|
end
|
@@ -319,7 +320,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
319
320
|
if @user.update(user_params)
|
320
321
|
redirect_to @user
|
321
322
|
else
|
322
|
-
render :edit
|
323
|
+
render :edit
|
323
324
|
end
|
324
325
|
end
|
325
326
|
end
|
@@ -332,7 +333,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
332
333
|
if @user.update(user_params)
|
333
334
|
redirect_to @user
|
334
335
|
else
|
335
|
-
render :edit
|
336
|
+
render :edit
|
336
337
|
end
|
337
338
|
end
|
338
339
|
end
|
@@ -342,11 +343,11 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
342
343
|
```ruby
|
343
344
|
class UsersController
|
344
345
|
def update
|
345
|
-
trx do |
|
346
|
+
trx do |t|
|
346
347
|
if @user.update(user_params)
|
347
|
-
|
348
|
+
t.after_commit { redirect_to @user }
|
348
349
|
else
|
349
|
-
|
350
|
+
t.after_commit { render :edit }
|
350
351
|
end
|
351
352
|
end
|
352
353
|
end
|
@@ -377,7 +378,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
377
378
|
if @post.tags_arr.include?('special')
|
378
379
|
@post.update_columns(special: true)
|
379
380
|
end
|
380
|
-
@post.mongo_post.update(special: @post.tags_arr.include?('special'))
|
381
|
+
@post.mongo_post.update(special: @post.tags_arr.include?('special'))
|
381
382
|
end
|
382
383
|
```
|
383
384
|
|
@@ -397,12 +398,12 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
397
398
|
#### Bad
|
398
399
|
```ruby
|
399
400
|
def some_method
|
400
|
-
trx do |
|
401
|
+
trx do |t|
|
401
402
|
user = User.find_by(email: email)
|
402
403
|
return user if user
|
403
404
|
|
404
405
|
user = User.create(email: email)
|
405
|
-
|
406
|
+
t.after_commit { Mailer.registration_confirmation(user.id).deliver_later }
|
406
407
|
end
|
407
408
|
end
|
408
409
|
```
|
@@ -414,7 +415,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
414
415
|
def some_method
|
415
416
|
puts "Start"
|
416
417
|
yield
|
417
|
-
puts "End"
|
418
|
+
puts "End"
|
418
419
|
end
|
419
420
|
|
420
421
|
def another_method
|
@@ -442,7 +443,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
442
443
|
wrap_in_trx def some_method
|
443
444
|
return if User.where(email: email).exists?
|
444
445
|
|
445
|
-
User.create(email: email)
|
446
|
+
User.create(email: email)
|
446
447
|
end
|
447
448
|
```
|
448
449
|
|
@@ -453,130 +454,7 @@ There is "On complete" feature that allows you to define callbacks(blocks of cod
|
|
453
454
|
return user if user
|
454
455
|
|
455
456
|
user = User.create(email: email)
|
456
|
-
trx { |
|
457
|
-
end
|
458
|
-
```
|
459
|
-
|
460
|
-
## On complete callbacks
|
461
|
-
|
462
|
-
On-complete callbacks are defined with `TrxExt::CallbackPool#on_complete` method. An instance of `TrxExt::CallbackPool` is passed in each transaction block. You may add as much on-complete callbacks as you want by calling `TrxExt::CallbackPool#on_complete` several times - they will be executed in the order you define
|
463
|
-
them(FIFO principle). The on-complete callbacks from nested transactions will be executed from the most deep to the most top transaction. Another words, if top transaction defines `<#TrxExt::CallbackPool 0x1>` instance and nested transaction defines `<#TrxExt::CallbackPool 0x2>` instance then, when executing on-complete callbacks - the callbacks of `<#TrxExt::CallbackPool 0x2>` instance will be executed first(FILO principle).
|
464
|
-
|
465
|
-
Example:
|
466
|
-
|
467
|
-
```ruby
|
468
|
-
ActiveRecord::Base.transaction do |c1|
|
469
|
-
User.first
|
470
|
-
c1.on_complete { puts "This is 3rd message" }
|
471
|
-
ActiveRecord::Base.transaction do |c2|
|
472
|
-
User.last
|
473
|
-
c2.on_complete { puts "This is 2nd message" }
|
474
|
-
ActiveRecord::Base.transaction do |c3|
|
475
|
-
c3.on_complete { puts "This is 1st message" }
|
476
|
-
User.first(2)
|
477
|
-
end
|
478
|
-
end
|
479
|
-
c1.on_complete { puts "This is 4th message" }
|
480
|
-
end
|
481
|
-
```
|
482
|
-
|
483
|
-
If you don't need to define on-complete callbacks - you may skip explicit definition of block's argument.
|
484
|
-
|
485
|
-
Example:
|
486
|
-
|
487
|
-
```ruby
|
488
|
-
ActiveRecord::Base.transaction { User.first }
|
489
|
-
```
|
490
|
-
|
491
|
-
Keep in mind, that all on-complete callbacks are not a part of the transaction. If you want to make it transactional - you need to wrap it in another transaction.
|
492
|
-
|
493
|
-
Example:
|
494
|
-
|
495
|
-
```ruby
|
496
|
-
ActiveRecord::Base.transaction do |c1|
|
497
|
-
User.first
|
498
|
-
c1.on_complete do
|
499
|
-
ActiveRecord::Base.transaction do
|
500
|
-
User.find_or_create_by(email: email)
|
501
|
-
end
|
502
|
-
end
|
503
|
-
end
|
504
|
-
```
|
505
|
-
|
506
|
-
You may define on-complete callbacks inside another on-complete callbacks. You may define another transactions in
|
507
|
-
on-complete callbacks. Just don't get confused in the order they are going to be executed.
|
508
|
-
|
509
|
-
Example:
|
510
|
-
|
511
|
-
```ruby
|
512
|
-
ActiveRecord::Base.transaction do |c1|
|
513
|
-
User.first
|
514
|
-
c1.on_complete do
|
515
|
-
puts "This line will be executed first"
|
516
|
-
ActiveRecord::Base.transaction do |c2|
|
517
|
-
User.last
|
518
|
-
c2.on_complete do
|
519
|
-
puts "This line will be executed second"
|
520
|
-
end
|
521
|
-
end
|
522
|
-
puts "This line will be executed third"
|
523
|
-
end
|
524
|
-
end
|
525
|
-
```
|
526
|
-
|
527
|
-
Also, please avoid usage of the callbacks that belong to one transaction in another transaction explicitly. This complicates the readability of the code.
|
528
|
-
|
529
|
-
Example:
|
530
|
-
|
531
|
-
```ruby
|
532
|
-
ActiveRecord::Base.transaction do |c1|
|
533
|
-
User.first
|
534
|
-
c1.on_complete do
|
535
|
-
ActiveRecord::Base.transaction do
|
536
|
-
User.last
|
537
|
-
c1.on_complete do
|
538
|
-
puts "This will be executed at the time when parent transaction's on-complete callbacks are executed!"
|
539
|
-
end
|
540
|
-
end
|
541
|
-
end
|
542
|
-
end
|
543
|
-
```
|
544
|
-
|
545
|
-
## On complete callbacks integrity
|
546
|
-
|
547
|
-
* Don't define callbacks blocks as lambdas unless you are 100% sure what you are doing. Lambda has a bit different behaviour comparing to Proc. Refer to [ruby documentation](https://ruby-doc.org/core-2.6.5/Proc.html#class-Proc-label-Lambda+and+non-lambda+semantics).
|
548
|
-
|
549
|
-
* When defining a callback - make sure that it does not depend on transaction's integrity. Another words - define it in a way like it is a normal code implementation outside the transaction:
|
550
|
-
|
551
|
-
#### Bad
|
552
|
-
```ruby
|
553
|
-
trx do |c|
|
554
|
-
user = User.find(id)
|
555
|
-
user.referrals.create(referral_attrs)
|
556
|
-
c.on_complete do
|
557
|
-
Mailer.new_referral(
|
558
|
-
user_id: user.id, total_referrals: user.referrals.count
|
559
|
-
).deliver_later
|
560
|
-
end
|
561
|
-
end
|
562
|
-
```
|
563
|
-
|
564
|
-
#### Explanation
|
565
|
-
The example above introduces two issues:
|
566
|
-
- `on_complete` callback does not depend on the result of `user.referrals.create(referral_attrs)`. And it should - we only need to send the email only if referral is created. Solution - add the condition for the `on_complete` callback
|
567
|
-
- the number of user's referrals `user.referrals.count` is calculated inside `on_complete`, but it should be calculated within the transaction. Solution - calculate referrals count in transaction, extract its value into local variable and use that variable in the `on_complete` callback
|
568
|
-
|
569
|
-
#### Good
|
570
|
-
```ruby
|
571
|
-
trx do |c|
|
572
|
-
user = User.find(id)
|
573
|
-
referral = user.referrals.create(referral_attrs)
|
574
|
-
if referral.persisted?
|
575
|
-
total_referrals = user.referrals.count
|
576
|
-
c.on_complete do
|
577
|
-
Mailer.new_referral(user_id: user.id, total_referrals: total_referrals).deliver_later
|
578
|
-
end
|
579
|
-
end
|
457
|
+
trx { |t| t.after_commit { Mailer.registration_confirmation(user.id).deliver_later } }
|
580
458
|
end
|
581
459
|
```
|
582
460
|
|
@@ -588,7 +466,7 @@ You can make any method atomic by wrapping it into transaction using `#wrap_in_t
|
|
588
466
|
class ApplicationRecord < ActiveRecord::Base
|
589
467
|
class << self
|
590
468
|
wrap_in_trx :find_or_create_by
|
591
|
-
wrap_in_trx :find_or_create_by!
|
469
|
+
wrap_in_trx :find_or_create_by!
|
592
470
|
end
|
593
471
|
|
594
472
|
wrap_in_trx def some_method
|
@@ -602,7 +480,7 @@ end
|
|
602
480
|
### Setup
|
603
481
|
|
604
482
|
- After checking out the repo, run `bundle install` to install dependencies.
|
605
|
-
- Run docker-compose using `docker
|
483
|
+
- Run docker-compose using `docker compose up` command - it starts necessary services
|
606
484
|
- Run next command to create dev and test databases:
|
607
485
|
|
608
486
|
```shell
|
data/bin/test_all_ar_versions
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# frozen_string_literal: true
|
4
4
|
|
5
|
-
%w[7.
|
5
|
+
%w[7.2].each do |ar_version|
|
6
6
|
`rm Gemfile.lock`
|
7
7
|
Process.waitpid(Kernel.spawn({ 'AR_VERSION' => ar_version }, "bundle install --quiet", close_others: true))
|
8
8
|
Process.waitpid(Kernel.spawn({ 'AR_VERSION' => ar_version }, "rspec", close_others: true))
|
data/lib/trx_ext/version.rb
CHANGED
data/lib/trx_ext.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'active_record'
|
4
|
-
require_relative "trx_ext/callback_pool"
|
5
4
|
require_relative "trx_ext/object_ext"
|
6
5
|
require_relative "trx_ext/retry"
|
7
|
-
require_relative "trx_ext/transaction"
|
8
6
|
require_relative "trx_ext/config"
|
9
7
|
require_relative "trx_ext/version"
|
10
8
|
|
@@ -39,12 +37,12 @@ module TrxExt
|
|
39
37
|
private
|
40
38
|
|
41
39
|
def integrate_into_class(klass)
|
42
|
-
klass.prepend TrxExt::Transaction
|
43
40
|
TrxExt::Retry.with_retry_until_serialized(klass, :exec_query)
|
44
41
|
TrxExt::Retry.with_retry_until_serialized(klass, :exec_insert)
|
45
42
|
TrxExt::Retry.with_retry_until_serialized(klass, :exec_delete)
|
46
43
|
TrxExt::Retry.with_retry_until_serialized(klass, :exec_update)
|
47
44
|
TrxExt::Retry.with_retry_until_serialized(klass, :exec_insert_all)
|
45
|
+
TrxExt::Retry.with_retry_until_serialized(klass, :transaction)
|
48
46
|
end
|
49
47
|
end
|
50
48
|
end
|
data/trx_ext.gemspec
CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.description = "Allow you to retry deadlocks, serialization errors, non-unique errors."
|
13
13
|
spec.homepage = "https://github.com/intale/trx_ext"
|
14
14
|
spec.license = "MIT"
|
15
|
-
spec.required_ruby_version = ">= 3.
|
15
|
+
spec.required_ruby_version = ">= 3.1.0"
|
16
16
|
|
17
17
|
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
18
|
|
@@ -31,5 +31,5 @@ Gem::Specification.new do |spec|
|
|
31
31
|
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
32
|
spec.require_paths = ["lib"]
|
33
33
|
|
34
|
-
spec.add_dependency 'activerecord', '>= 7.
|
34
|
+
spec.add_dependency 'activerecord', '>= 7.2', '< 8'
|
35
35
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trx_ext
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 3.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Dzyzenko
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,14 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '7.
|
19
|
+
version: '7.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '8'
|
20
23
|
type: :runtime
|
21
24
|
prerelease: false
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
23
26
|
requirements:
|
24
27
|
- - ">="
|
25
28
|
- !ruby/object:Gem::Version
|
26
|
-
version: '7.
|
29
|
+
version: '7.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '8'
|
27
33
|
description: Allow you to retry deadlocks, serialization errors, non-unique errors.
|
28
34
|
email:
|
29
35
|
- ivan.dzyzenko@gmail.com
|
@@ -89,11 +95,9 @@ files:
|
|
89
95
|
- dummy_app/tmp/.keep
|
90
96
|
- dummy_app/vendor/.keep
|
91
97
|
- lib/trx_ext.rb
|
92
|
-
- lib/trx_ext/callback_pool.rb
|
93
98
|
- lib/trx_ext/config.rb
|
94
99
|
- lib/trx_ext/object_ext.rb
|
95
100
|
- lib/trx_ext/retry.rb
|
96
|
-
- lib/trx_ext/transaction.rb
|
97
101
|
- lib/trx_ext/version.rb
|
98
102
|
- log/.gitkeep
|
99
103
|
- trx_ext.gemspec
|
@@ -103,8 +107,8 @@ licenses:
|
|
103
107
|
metadata:
|
104
108
|
allowed_push_host: https://rubygems.org
|
105
109
|
homepage_uri: https://github.com/intale/trx_ext
|
106
|
-
source_code_uri: https://github.com/intale/trx_ext/tree/
|
107
|
-
changelog_uri: https://github.com/intale/trx_ext/blob/
|
110
|
+
source_code_uri: https://github.com/intale/trx_ext/tree/v3.0.0
|
111
|
+
changelog_uri: https://github.com/intale/trx_ext/blob/v3.0.0/CHANGELOG.md
|
108
112
|
post_install_message:
|
109
113
|
rdoc_options: []
|
110
114
|
require_paths:
|
@@ -113,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
117
|
requirements:
|
114
118
|
- - ">="
|
115
119
|
- !ruby/object:Gem::Version
|
116
|
-
version: 3.
|
120
|
+
version: 3.1.0
|
117
121
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
122
|
requirements:
|
119
123
|
- - ">="
|
@@ -1,108 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TrxExt
|
4
|
-
class CallbackPool
|
5
|
-
class << self
|
6
|
-
# @param :previous [nil, TrxExt::CallbackPool]
|
7
|
-
# @return [TrxExt::CallbackPool]
|
8
|
-
def add(previous: nil)
|
9
|
-
# It may happen when transaction is defined inside `on_complete` callback and, thus, when adding the
|
10
|
-
# `on_complete` callback for it, `previous` will point on the `TrxExt::CallbackPool` of the transaction's
|
11
|
-
# callback that is being executing right now. We should not continue such chain and allow the transaction to
|
12
|
-
# build its own chain.
|
13
|
-
# Example:
|
14
|
-
# trx do |c1|
|
15
|
-
# c1.on_complete do
|
16
|
-
# # When executing .add to define c2 TrxExt::CallbackPool - previous argument will contain
|
17
|
-
# # c1 TrxExt::CallbackPool that is already being executing. Assign nil to previous in this case.
|
18
|
-
# trx do |c2|
|
19
|
-
# c2.on_complete { }
|
20
|
-
# end
|
21
|
-
# end
|
22
|
-
# end
|
23
|
-
previous = nil if previous&.locked_for_execution?
|
24
|
-
inst = new
|
25
|
-
inst.previous = previous
|
26
|
-
inst
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# Points on the previous instance in the single linked chain
|
31
|
-
attr_accessor :previous
|
32
|
-
attr_writer :locked_for_execution
|
33
|
-
|
34
|
-
def initialize
|
35
|
-
@callbacks = []
|
36
|
-
@locked_for_execution = false
|
37
|
-
end
|
38
|
-
|
39
|
-
# @return [Boolean] whether current instance is locked for the next {#exec_callbacks} action
|
40
|
-
def locked_for_execution?
|
41
|
-
@locked_for_execution
|
42
|
-
end
|
43
|
-
|
44
|
-
# @param blk [Proc]
|
45
|
-
# @return [void]
|
46
|
-
def on_complete(&blk)
|
47
|
-
@callbacks.push(blk)
|
48
|
-
end
|
49
|
-
|
50
|
-
# The chain of callbacks pool comes as follows:
|
51
|
-
# <#TrxExt::CallbackPool:0x03 @callbacks=[] previous=
|
52
|
-
# <#TrxExt::CallbackPool:0x02 @callbacks=[] previous=
|
53
|
-
# <#TrxExt::CallbackPool:0x01 @callbacks=[] previous=nil>
|
54
|
-
# >
|
55
|
-
# >
|
56
|
-
# The most inner instance - is the instance that was created first in stack call. The most top instance - is the
|
57
|
-
# instance that was created last in the stack call.
|
58
|
-
# Related example of how they are created during `trx` calls:
|
59
|
-
# trx do |c0x01|
|
60
|
-
# trx do |c0x02|
|
61
|
-
# trx do |c0x03|
|
62
|
-
# end
|
63
|
-
# end
|
64
|
-
# end
|
65
|
-
#
|
66
|
-
# At the end of execution of each `trx` - {#exec_callbacks_chain} will be called for each {TrxExt::CallbackPool}.
|
67
|
-
# But only <#TrxExt::CallbackPool:0x01> will really execute the callbacks of all pools in the chain - only it has
|
68
|
-
# rights to do this, because only it stands on the top of the call stack. This is ensured with
|
69
|
-
# `return unless previous.nil?` condition. At the moment of execution of {#exec_callbacks_chain} of top-stack
|
70
|
-
# instance - {ActiveRecord::Base.connection.current_callbacks_chain_link} points on the most inner, by call stack,
|
71
|
-
# {TrxExt::CallbackPool}. In the example above, it is <#TrxExt::CallbackPool:0x03>
|
72
|
-
#
|
73
|
-
# @param :connection [ActiveRecord::ConnectionAdapters::PostgreSQLAdapter]
|
74
|
-
# @return [Boolean] whether callbacks was executed
|
75
|
-
def exec_callbacks_chain(connection:)
|
76
|
-
return false unless previous.nil?
|
77
|
-
|
78
|
-
current = connection.current_callbacks_chain_link
|
79
|
-
loop do
|
80
|
-
# It is important to keep it here to prevent potential
|
81
|
-
# `NoMethodError: undefined method `exec_callbacks' for nil:NilClass` exception when trying to execute callbacks
|
82
|
-
# for the transaction that raised an exception. In case of exception - current_callbacks_chain_link is set to
|
83
|
-
# nil. See {TrxExt::Retry.retry_until_serialized}. See {TrxExt::Transaction#transaction}.
|
84
|
-
# Example:
|
85
|
-
# trx { raise "trol" } # Should raise RuntimeError instead of NoMethodError
|
86
|
-
break if current.nil?
|
87
|
-
|
88
|
-
current.locked_for_execution = true
|
89
|
-
current.exec_callbacks
|
90
|
-
current = current.previous
|
91
|
-
end
|
92
|
-
# Can't use `ensure` here, because it will be triggered even if condition in first line is falsey. And we need
|
93
|
-
# to set connection#current_callbacks_chain_link to nil only in case of exception or in case of successful run of
|
94
|
-
# callbacks
|
95
|
-
connection.current_callbacks_chain_link = nil
|
96
|
-
true
|
97
|
-
rescue
|
98
|
-
connection.current_callbacks_chain_link = nil
|
99
|
-
raise
|
100
|
-
end
|
101
|
-
|
102
|
-
# @return [void]
|
103
|
-
def exec_callbacks
|
104
|
-
@callbacks.each(&:call)
|
105
|
-
nil
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
data/lib/trx_ext/transaction.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module TrxExt
|
4
|
-
# Implements the feature that allows you to define callbacks that will be fired after SQL transaction is complete.
|
5
|
-
module Transaction
|
6
|
-
# See https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-transaction
|
7
|
-
# for available params
|
8
|
-
def transaction(**kwargs, &blk)
|
9
|
-
pool = nil
|
10
|
-
TrxExt::Retry.retry_until_serialized(self) do
|
11
|
-
super(**kwargs) do
|
12
|
-
pool = TrxExt::CallbackPool.add(previous: current_callbacks_chain_link)
|
13
|
-
self.current_callbacks_chain_link = pool
|
14
|
-
blk.call(pool)
|
15
|
-
end
|
16
|
-
rescue
|
17
|
-
self.current_callbacks_chain_link = nil
|
18
|
-
raise
|
19
|
-
end
|
20
|
-
ensure
|
21
|
-
pool.exec_callbacks_chain(connection: self)
|
22
|
-
end
|
23
|
-
|
24
|
-
# Returns the {TrxExt::CallbackPool} instance for the transaction that is being executed at the moment.
|
25
|
-
def current_callbacks_chain_link
|
26
|
-
@trx_callbacks_chain
|
27
|
-
end
|
28
|
-
|
29
|
-
# Set the {TrxExt::CallbackPool} instance for the transaction that is being executed at the moment.
|
30
|
-
def current_callbacks_chain_link=(val)
|
31
|
-
@trx_callbacks_chain = val
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|