trx_ext 2.0.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b300fb55d7310a3e3f4d3a8860044f48deec84791caf6303d7a6b01a2e2728b7
4
- data.tar.gz: 2e6d704c74c209b515dfc3a04d06ca549322a0e33d1037a414d779df1c063afd
3
+ metadata.gz: 437621539108dd313b7d2b91383ca64009bc64472cb71a67e851f1eb76b3a894
4
+ data.tar.gz: 6b18b50ebdd5ac8b91fc7f5fd870cdc30b2d8a421a5ba7c2d3a42620b9460218
5
5
  SHA512:
6
- metadata.gz: 45c7190466168fe05b18ad69f084152b638235f9fab14edaa0eb6962449a2012b71e6e2b991fb319f73080cd7e3aba2f6700203a362cd86483d91f8ca606b66c
7
- data.tar.gz: ce496ee423f04d12b9f92af4373e460edc31048d90908333f8449d98d70eb61053fd675c94f2345e767c5a43ae5535fa6b0e964f179dce604513aa782a8466e4
6
+ metadata.gz: de55ddb63c774c507bcb2c88e13f55f34d97fa4c16576eb77c12b8dc27ab61a70f00790715a60ddc7b5338ad6db40733e66878264e535389dd210ab8da172b9c
7
+ data.tar.gz: ed336131965bca0cee5818f39d17e025cd89da876fbbcb5084d2f7028fb00bf39a1297e2df88196eb9b130ede88bd312b480ccf2119c94277880aeef0030348c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,8 @@
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
+
3
6
  ## [2.0.1] - 2024-09-20
4
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:
5
8
 
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. It also allows you to define `on_complete` callback that is being executed after SQL transaction is finished(either COMMIT-ed or ROLLBACK-ed). 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:
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 patches ActiveRecord - 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.
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.1+
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 |c|
50
- c.on_complete { puts "This message will be printed after COMMIT statement." }
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 |c|
57
- c.on_complete { puts "This message will be printed after ROLLBACK statement." }
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 |c|
95
+ TrxExt.configure do |config|
95
96
  # Number of retries before failing when unique constraint error raises. Default is 5
96
- c.unique_retries = 5
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 `on_complete` callback to simplify your code:
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 |c|
260
+ trx do |t|
260
261
  user = User.find_or_initialize_by(email: email)
261
262
  if user.save
262
- c.on_complete { Mailer.registration_confirmation(user.id).deliver_later }
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 |c|
346
+ trx do |t|
346
347
  if @user.update(user_params)
347
- c.on_complete { redirect_to @user }
348
+ t.after_commit { redirect_to @user }
348
349
  else
349
- c.on_complete { render :edit }
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 |c|
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
- c.on_complete { Mailer.registration_confirmation(user.id).deliver_later }
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 { |c| c.on_complete { Mailer.registration_confirmation(user.id).deliver_later } }
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-compose up` command - it starts necessary services
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # frozen_string_literal: true
4
4
 
5
- %w[7.1].each do |ar_version|
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))
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TrxExt
4
- VERSION = "2.0.1"
4
+ VERSION = "3.0.0"
5
5
  end
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.0.0"
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.1', '< 7.2'
34
+ spec.add_dependency 'activerecord', '>= 7.2', '< 8'
35
35
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: trx_ext
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
@@ -16,20 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '7.1'
19
+ version: '7.2'
20
20
  - - "<"
21
21
  - !ruby/object:Gem::Version
22
- version: '7.2'
22
+ version: '8'
23
23
  type: :runtime
24
24
  prerelease: false
25
25
  version_requirements: !ruby/object:Gem::Requirement
26
26
  requirements:
27
27
  - - ">="
28
28
  - !ruby/object:Gem::Version
29
- version: '7.1'
29
+ version: '7.2'
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
- version: '7.2'
32
+ version: '8'
33
33
  description: Allow you to retry deadlocks, serialization errors, non-unique errors.
34
34
  email:
35
35
  - ivan.dzyzenko@gmail.com
@@ -95,11 +95,9 @@ files:
95
95
  - dummy_app/tmp/.keep
96
96
  - dummy_app/vendor/.keep
97
97
  - lib/trx_ext.rb
98
- - lib/trx_ext/callback_pool.rb
99
98
  - lib/trx_ext/config.rb
100
99
  - lib/trx_ext/object_ext.rb
101
100
  - lib/trx_ext/retry.rb
102
- - lib/trx_ext/transaction.rb
103
101
  - lib/trx_ext/version.rb
104
102
  - log/.gitkeep
105
103
  - trx_ext.gemspec
@@ -109,8 +107,8 @@ licenses:
109
107
  metadata:
110
108
  allowed_push_host: https://rubygems.org
111
109
  homepage_uri: https://github.com/intale/trx_ext
112
- source_code_uri: https://github.com/intale/trx_ext/tree/v2.0.1
113
- changelog_uri: https://github.com/intale/trx_ext/blob/v2.0.1/CHANGELOG.md
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
114
112
  post_install_message:
115
113
  rdoc_options: []
116
114
  require_paths:
@@ -119,7 +117,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
119
117
  requirements:
120
118
  - - ">="
121
119
  - !ruby/object:Gem::Version
122
- version: 3.0.0
120
+ version: 3.1.0
123
121
  required_rubygems_version: !ruby/object:Gem::Requirement
124
122
  requirements:
125
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
@@ -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