trx_ext 1.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 +7 -0
- data/.rspec +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +12 -0
- data/LICENSE.txt +21 -0
- data/README.md +622 -0
- data/Rakefile +4 -0
- data/bin/console +29 -0
- data/bin/setup +8 -0
- data/bin/test_all_ar_versions +11 -0
- data/lib/trx_ext/callback_pool.rb +108 -0
- data/lib/trx_ext/config.rb +12 -0
- data/lib/trx_ext/object_ext.rb +42 -0
- data/lib/trx_ext/railtie.rb +9 -0
- data/lib/trx_ext/retry.rb +64 -0
- data/lib/trx_ext/transaction.rb +34 -0
- data/lib/trx_ext/version.rb +6 -0
- data/lib/trx_ext.rb +55 -0
- data/log/.gitkeep +0 -0
- data/trx_ext.gemspec +41 -0
- metadata +167 -0
@@ -0,0 +1,108 @@
|
|
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
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TrxExt
|
4
|
+
module ObjectExt
|
5
|
+
# Wraps specified method in an +ActiveRecord+ transaction.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# class Tilapia < Symbology::Base
|
9
|
+
# wrap_in_trx def gnosis(number)
|
10
|
+
# introspect(number, string.numerology(:kabbalah).sum)
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# order = Tilapia.new
|
15
|
+
# order.gnosis(93)
|
16
|
+
# # (0.6ms) BEGIN
|
17
|
+
# # Introspection Load (0.4ms) SELECT "introspections".* FROM "introspections" WHERE "introspections"."id" = $1 LIMIT 1 [["id", 93]]
|
18
|
+
# # (0.2ms) COMMIT
|
19
|
+
# # => 418
|
20
|
+
#
|
21
|
+
# @param method [Symbol] a name of the method
|
22
|
+
# @return [Symbol]
|
23
|
+
def wrap_in_trx(method)
|
24
|
+
module_to_prepend = Module.new do
|
25
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
26
|
+
def #{method}(...)
|
27
|
+
trx do
|
28
|
+
super
|
29
|
+
end
|
30
|
+
end
|
31
|
+
RUBY
|
32
|
+
end
|
33
|
+
prepend module_to_prepend
|
34
|
+
method
|
35
|
+
end
|
36
|
+
|
37
|
+
# A shorthand version of <tt>ActiveRecord::Base.transaction</tt>
|
38
|
+
def trx(...)
|
39
|
+
ActiveRecord::Base.transaction(...)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TrxExt
|
4
|
+
module Retry
|
5
|
+
class << self
|
6
|
+
# Wraps specified method in a +TrxExt::Retry.retry_until_serialized+ loop.
|
7
|
+
#
|
8
|
+
# @param klass [Class] class a method belongs to
|
9
|
+
# @param method [Symbol] instance method that needs to be wrapped into +TrxExt::Retry.retry_until_serialized+
|
10
|
+
def with_retry_until_serialized(klass, method)
|
11
|
+
module_to_prepend = Module.new do
|
12
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
13
|
+
def #{method}(...)
|
14
|
+
::TrxExt::Retry.retry_until_serialized do
|
15
|
+
super
|
16
|
+
end
|
17
|
+
end
|
18
|
+
RUBY
|
19
|
+
end
|
20
|
+
prepend module_to_prepend
|
21
|
+
method
|
22
|
+
end
|
23
|
+
|
24
|
+
# Retries block execution until serialization errors are no longer raised
|
25
|
+
def retry_until_serialized
|
26
|
+
retries_count = 0
|
27
|
+
begin
|
28
|
+
yield
|
29
|
+
rescue => error
|
30
|
+
error_classification = error_classification(error)
|
31
|
+
if error_classification == :record_not_unique
|
32
|
+
retries_count += 1
|
33
|
+
end
|
34
|
+
if retry_query?(error, retries_count)
|
35
|
+
TrxExt.log("Detected transaction rollback condition. Reason - #{error_classification}. Retrying...")
|
36
|
+
retry
|
37
|
+
else
|
38
|
+
raise error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def error_classification(error)
|
46
|
+
case
|
47
|
+
when error.message.index('deadlock detected')
|
48
|
+
:deadlock
|
49
|
+
when error.message.index('could not serialize')
|
50
|
+
:serialization_error
|
51
|
+
when error.class == ActiveRecord::RecordNotUnique
|
52
|
+
:record_not_unique
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def retry_query?(error, retryies_count)
|
57
|
+
classification = error_classification(error)
|
58
|
+
ActiveRecord::Base.connection.open_transactions == 0 &&
|
59
|
+
(%i(deadlock serialization_error).include?(classification) ||
|
60
|
+
classification == :record_not_unique && retryies_count < TrxExt.config.unique_retries)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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 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
|
data/lib/trx_ext.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require_relative "trx_ext/callback_pool"
|
5
|
+
require_relative "trx_ext/object_ext"
|
6
|
+
require_relative "trx_ext/retry"
|
7
|
+
require_relative "trx_ext/transaction"
|
8
|
+
require_relative "trx_ext/config"
|
9
|
+
require_relative "trx_ext/version"
|
10
|
+
|
11
|
+
module TrxExt
|
12
|
+
class << self
|
13
|
+
attr_accessor :logger
|
14
|
+
|
15
|
+
# @return [void]
|
16
|
+
def integrate!
|
17
|
+
ActiveSupport.on_load(:active_record) do
|
18
|
+
require 'active_record/connection_adapters/postgresql_adapter'
|
19
|
+
|
20
|
+
# Allow to use #wrap_in_trx and #trx methods everywhere
|
21
|
+
Object.prepend(TrxExt::ObjectExt)
|
22
|
+
|
23
|
+
# Patch #transaction
|
24
|
+
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.prepend(TrxExt::Transaction)
|
25
|
+
|
26
|
+
# Single SELECT/UPDATE/DELETE queries should also be retried even if they are not a part of explicit transaction
|
27
|
+
TrxExt::Retry.with_retry_until_serialized(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, :exec_query)
|
28
|
+
TrxExt::Retry.with_retry_until_serialized(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, :exec_update)
|
29
|
+
TrxExt::Retry.with_retry_until_serialized(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter, :exec_delete)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [void]
|
34
|
+
def log(msg)
|
35
|
+
return unless logger
|
36
|
+
|
37
|
+
logger.info(msg)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [TrxExt::Config]
|
41
|
+
def config
|
42
|
+
@config ||= TrxExt::Config.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def configure
|
46
|
+
yield config
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
if defined?(Rails::Railtie)
|
52
|
+
require_relative "trx_ext/railtie"
|
53
|
+
else
|
54
|
+
TrxExt.integrate!
|
55
|
+
end
|
data/log/.gitkeep
ADDED
File without changes
|
data/trx_ext.gemspec
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/trx_ext/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "trx_ext"
|
7
|
+
spec.version = TrxExt::VERSION
|
8
|
+
spec.authors = ["Ivan Dzyzenko"]
|
9
|
+
spec.email = ["ivan.dzyzenko@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "ActiveRecord's transaction extension"
|
12
|
+
spec.description = "Allow you to retry deadlocks, serialization errors, non-unique errors."
|
13
|
+
spec.homepage = "https://github.com/intale/trx_ext"
|
14
|
+
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = ">= 3.0.0"
|
16
|
+
|
17
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
18
|
+
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/intale/trx_ext/tree/v#{spec.version}"
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/intale/trx_ext/blob/v#{spec.version}/CHANGELOG.md"
|
22
|
+
|
23
|
+
# Specify which files should be added to the gem when it is released.
|
24
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
25
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
26
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
spec.bindir = "exe"
|
31
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
32
|
+
spec.require_paths = ["lib"]
|
33
|
+
|
34
|
+
spec.add_dependency 'activerecord', '~> 6'
|
35
|
+
spec.add_dependency 'pg', '~> 1.2'
|
36
|
+
spec.add_development_dependency 'rspec', "~> 3.10"
|
37
|
+
spec.add_development_dependency 'timecop', "~> 0.9.4"
|
38
|
+
spec.add_development_dependency 'factory_bot', "~> 6.2"
|
39
|
+
spec.add_development_dependency 'fivemat', '~> 1.3'
|
40
|
+
spec.add_development_dependency 'rspec-its', '~> 1.3'
|
41
|
+
end
|
metadata
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: trx_ext
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ivan Dzyzenko
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-12-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '6'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.10'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.10'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: timecop
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.9.4
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.9.4
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: factory_bot
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '6.2'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '6.2'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: fivemat
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rspec-its
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '1.3'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '1.3'
|
111
|
+
description: Allow you to retry deadlocks, serialization errors, non-unique errors.
|
112
|
+
email:
|
113
|
+
- ivan.dzyzenko@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".rspec"
|
119
|
+
- ".ruby-gemset"
|
120
|
+
- ".ruby-version"
|
121
|
+
- CHANGELOG.md
|
122
|
+
- CODE_OF_CONDUCT.md
|
123
|
+
- Gemfile
|
124
|
+
- LICENSE.txt
|
125
|
+
- README.md
|
126
|
+
- Rakefile
|
127
|
+
- bin/console
|
128
|
+
- bin/setup
|
129
|
+
- bin/test_all_ar_versions
|
130
|
+
- lib/trx_ext.rb
|
131
|
+
- lib/trx_ext/callback_pool.rb
|
132
|
+
- lib/trx_ext/config.rb
|
133
|
+
- lib/trx_ext/object_ext.rb
|
134
|
+
- lib/trx_ext/railtie.rb
|
135
|
+
- lib/trx_ext/retry.rb
|
136
|
+
- lib/trx_ext/transaction.rb
|
137
|
+
- lib/trx_ext/version.rb
|
138
|
+
- log/.gitkeep
|
139
|
+
- trx_ext.gemspec
|
140
|
+
homepage: https://github.com/intale/trx_ext
|
141
|
+
licenses:
|
142
|
+
- MIT
|
143
|
+
metadata:
|
144
|
+
allowed_push_host: https://rubygems.org
|
145
|
+
homepage_uri: https://github.com/intale/trx_ext
|
146
|
+
source_code_uri: https://github.com/intale/trx_ext/tree/v1.0.0
|
147
|
+
changelog_uri: https://github.com/intale/trx_ext/blob/v1.0.0/CHANGELOG.md
|
148
|
+
post_install_message:
|
149
|
+
rdoc_options: []
|
150
|
+
require_paths:
|
151
|
+
- lib
|
152
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: 3.0.0
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
requirements: []
|
163
|
+
rubygems_version: 3.2.28
|
164
|
+
signing_key:
|
165
|
+
specification_version: 4
|
166
|
+
summary: ActiveRecord's transaction extension
|
167
|
+
test_files: []
|