transaction_retry_continued 1.1.0 → 1.2.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/.github/workflows/main.yml +38 -18
- data/.gitignore +2 -0
- data/Gemfile +4 -17
- data/README.md +23 -8
- data/Rakefile +5 -3
- data/docker/ruby-2.5/Dockerfile +6 -0
- data/docker/ruby-2.7/Dockerfile +6 -0
- data/docker/ruby-3.0/Dockerfile +6 -0
- data/docker/ruby-3.1/Dockerfile +6 -0
- data/docker/test-ruby-2.5.sh +26 -0
- data/docker/test-ruby-2.7.sh +29 -0
- data/docker/test-ruby-3.0.sh +26 -0
- data/docker/test-ruby-3.1.sh +26 -0
- data/docker/test-ruby.sh +8 -0
- data/docker-compose.yml +60 -0
- data/gemfiles/Gemfile.base +5 -2
- data/lib/transaction_retry/active_record/base.rb +39 -39
- data/lib/transaction_retry/version.rb +3 -1
- data/lib/transaction_retry.rb +9 -9
- data/lib/transaction_retry_continued.rb +2 -0
- data/test/db/all.rb +2 -0
- data/test/db/db.rb +15 -15
- data/test/db/migrations.rb +6 -7
- data/test/db/queued_job.rb +2 -0
- data/test/integration/active_record/base/transaction_with_retry_test.rb +37 -42
- data/test/library_setup.rb +11 -9
- data/test/test_helper.rb +9 -6
- data/test/test_runner.rb +3 -1
- data/transaction_retry_continued.gemspec +15 -14
- metadata +22 -16
- data/d +0 -1
- data/test/test_console.rb +0 -11
- data/tests +0 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9593b4c28a4caca5aefedbd6543e7407b194216469bbab86f82e346afea00af1
|
4
|
+
data.tar.gz: 61ce1f23b2dbbc67af572171fcb5c369e9133d3aca95987d6148d333ee34bd76
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a502cf67b25e82cda4ab5aa3f1630a81b7e8effaf04cbbce10d1f94be7cba8768640c5d8ccd0da855909dc8156298b301cbfc802209bd69b4d33bc79ca8b7890
|
7
|
+
data.tar.gz: 817ecdee03df757c9f001e43fd68ef5943bad51435411234747ee4a8fe95a74b3a63b58d86094b099c69f5726ff8158654db6ec81687c814eb4dccf7839df23d
|
data/.github/workflows/main.yml
CHANGED
@@ -108,7 +108,7 @@ jobs:
|
|
108
108
|
POSTGRESQL_DB_PASS: database
|
109
109
|
POSTGRESQL_DB_NAME: transaction_retry_continued_test
|
110
110
|
steps:
|
111
|
-
- uses: actions/checkout@
|
111
|
+
- uses: actions/checkout@v4
|
112
112
|
- name: Set up Ruby
|
113
113
|
uses: ruby/setup-ruby@v1
|
114
114
|
with:
|
@@ -116,6 +116,12 @@ jobs:
|
|
116
116
|
bundler-cache: true
|
117
117
|
- name: Run bundle update
|
118
118
|
run: bundle update
|
119
|
+
- name: Setup Code Climate test-reporter
|
120
|
+
if: github.ref_name == 'master'
|
121
|
+
run: |
|
122
|
+
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
123
|
+
chmod +x ./cc-test-reporter
|
124
|
+
./cc-test-reporter before-build
|
119
125
|
- name: Start Mysql
|
120
126
|
if: matrix.db == 'mysql2'
|
121
127
|
run: |
|
@@ -158,20 +164,34 @@ jobs:
|
|
158
164
|
- name: Shutdown database
|
159
165
|
if: always() && (matrix.db == 'postgresql' || matrix.db == 'mysql2')
|
160
166
|
run: docker stop database
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
167
|
+
- name: Format coverage
|
168
|
+
if: github.ref_name == 'master'
|
169
|
+
run: |
|
170
|
+
./cc-test-reporter format-coverage -t simplecov -o coverage/cc_resultset.json
|
171
|
+
- name: Upload reports' artifacts
|
172
|
+
if: success() || failure()
|
173
|
+
uses: actions/upload-artifact@v4
|
174
|
+
with:
|
175
|
+
name: coverage_artifact_ruby_${{ matrix.ruby }}_ar_${{ matrix.activerecord }}_db_${{ matrix.db }}
|
176
|
+
if-no-files-found: ignore
|
177
|
+
path: coverage
|
178
|
+
retention-days: 1
|
179
|
+
finish:
|
180
|
+
needs: test
|
181
|
+
runs-on: ubuntu-latest
|
182
|
+
if: github.ref_name == 'master'
|
183
|
+
steps:
|
184
|
+
- uses: actions/checkout@v4
|
185
|
+
- name: Download Code Climate test-reporter
|
186
|
+
run: |
|
187
|
+
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
|
188
|
+
chmod +x ./cc-test-reporter
|
189
|
+
- name: Download reports' artifacts
|
190
|
+
uses: actions/download-artifact@v4
|
191
|
+
with:
|
192
|
+
path: coverage_reports
|
193
|
+
pattern: coverage_artifact_*
|
194
|
+
- name: sum-coverage
|
195
|
+
run: |
|
196
|
+
./cc-test-reporter sum-coverage coverage_reports/coverage_artifact_ruby*/cc_resultset.json -o coverage/codeclimate.json
|
197
|
+
./cc-test-reporter upload-coverage -r ${{secrets.CC_TEST_REPORTER_ID}} -i coverage/codeclimate.json
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
@@ -1,23 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'http://rubygems.org'
|
4
4
|
|
5
5
|
gemspec
|
6
6
|
|
7
|
-
gemfile_local = File.expand_path '
|
7
|
+
gemfile_local = File.expand_path 'Gemfile.local', __dir__
|
8
8
|
eval File.read(gemfile_local), binding, gemfile_local if File.exist? gemfile_local
|
9
|
-
|
10
|
-
|
11
|
-
# gem 'transaction_isolation', git: 'https://github.com/alittlebit/transaction_isolation'
|
12
|
-
# # Use the gem instead of a dated version bundled with Ruby
|
13
|
-
# gem 'minitest', '5.3.4'
|
14
|
-
# gem 'simplecov', :require => false
|
15
|
-
# end
|
16
|
-
#
|
17
|
-
# group :development do
|
18
|
-
# gem 'transaction_isolation', git: 'https://github.com/alittlebit/transaction_isolation'
|
19
|
-
# gem 'rake'
|
20
|
-
# # enhance irb
|
21
|
-
# gem 'awesome_print', :require => false
|
22
|
-
# gem 'pry', :require => false
|
23
|
-
# end
|
9
|
+
|
10
|
+
gem 'rubocop', '~> 1.64'
|
data/README.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
[](https://badge.fury.io/rb/transaction_retry_continued)
|
2
|
+
[](https://codeclimate.com/github/iagopiimenta/transaction_retry_continued/maintainability)
|
3
|
+
[](https://codeclimate.com/github/iagopiimenta/transaction_retry_continued/test_coverage)
|
4
|
+
[](https://github.com/iagopiimenta/transaction_retry_continued/actions/workflows/main.yml)
|
5
|
+
|
1
6
|
# Transaction Retry Continued
|
2
7
|
|
3
8
|
> This is a community-driven continuation of the [`transaction_retry`](https://github.com/qertoip/transaction_retry) gem
|
@@ -31,7 +36,7 @@ __It works out of the box with Ruby on Rails__.
|
|
31
36
|
If you have a standalone ActiveRecord-based project you'll need to call:
|
32
37
|
|
33
38
|
```ruby
|
34
|
-
|
39
|
+
TransactionRetry.apply_activerecord_patch # after connecting to the database
|
35
40
|
```
|
36
41
|
|
37
42
|
## Database deadlock and serialization errors that are retried
|
@@ -83,13 +88,23 @@ This gem was initially developed for and successfully works in production at [Ko
|
|
83
88
|
|
84
89
|
Run tests on the selected database (mysql2 by default):
|
85
90
|
|
86
|
-
|
87
|
-
|
88
|
-
|
91
|
+
```bash
|
92
|
+
# passing desired database, active record version and ruby version
|
93
|
+
docker compose run -e db=sqlite3 -e BUNDLE_GEMFILE=gemfiles/activerecord-7.0/Gemfile.sqlite3 ruby_2_5 bash -c ./docker/test-ruby.sh
|
94
|
+
|
95
|
+
# db options: mysql2, postgresql, sqlite3
|
96
|
+
# active record version options: 5.2, 6.0, 6.1, 7.0
|
97
|
+
# ruby version options: 2.5, 2.7, 3.0, 3.1
|
98
|
+
```
|
89
99
|
|
90
|
-
Run tests on all supported databases:
|
100
|
+
Run tests on all supported databases by ruby version:
|
91
101
|
|
92
|
-
|
102
|
+
```bash
|
103
|
+
docker compose up ruby_2_5
|
104
|
+
docker compose up ruby_2_7
|
105
|
+
docker compose up ruby_3_0
|
106
|
+
docker compose up ruby_3_1
|
107
|
+
```
|
93
108
|
|
94
109
|
Database configuration is hardcoded in test/db/db.rb; feel free to improve this and submit a pull request.
|
95
110
|
|
@@ -99,8 +114,8 @@ You should be very suspicious about any gem that monkey patches your stock Ruby
|
|
99
114
|
|
100
115
|
This gem is carefully written to not be more intrusive than it needs to be:
|
101
116
|
|
102
|
-
|
103
|
-
|
117
|
+
- wraps ActiveRecord::Base#transaction class method using alias_method to add new behaviour
|
118
|
+
- introduces two new private class methods in ActiveRecord::Base (with names that should never collide)
|
104
119
|
|
105
120
|
## License
|
106
121
|
|
data/Rakefile
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
2
4
|
|
3
5
|
require 'rake/testtask'
|
4
6
|
|
5
7
|
Rake::TestTask.new do |t|
|
6
|
-
t.libs += [
|
8
|
+
t.libs += %w[test lib]
|
7
9
|
t.pattern = 'test/integration/**/*_test.rb'
|
8
10
|
t.verbose = true
|
9
11
|
end
|
10
12
|
|
11
|
-
task :
|
13
|
+
task default: [:test]
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# fix conflicts between different ruby versions
|
4
|
+
rm -rf gemfiles/**/Gemfile.*.lock
|
5
|
+
|
6
|
+
gemfiles=(
|
7
|
+
"gemfiles/activerecord-5.2/Gemfile.sqlite3"
|
8
|
+
"gemfiles/activerecord-6.0/Gemfile.sqlite3"
|
9
|
+
"gemfiles/activerecord-6.1/Gemfile.sqlite3"
|
10
|
+
|
11
|
+
"gemfiles/activerecord-5.2/Gemfile.mysql2"
|
12
|
+
"gemfiles/activerecord-6.0/Gemfile.mysql2"
|
13
|
+
"gemfiles/activerecord-6.1/Gemfile.mysql2"
|
14
|
+
|
15
|
+
"gemfiles/activerecord-5.2/Gemfile.postgresql"
|
16
|
+
"gemfiles/activerecord-6.0/Gemfile.postgresql"
|
17
|
+
"gemfiles/activerecord-6.1/Gemfile.postgresql"
|
18
|
+
)
|
19
|
+
|
20
|
+
for gemfile in "${gemfiles[@]}"; do
|
21
|
+
database=$(echo $gemfile | cut -d'.' -f3)
|
22
|
+
|
23
|
+
echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
24
|
+
BUNDLE_GEMFILE=$gemfile bundle install
|
25
|
+
BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
26
|
+
done
|
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# fix conflicts between different ruby versions
|
4
|
+
rm -rf gemfiles/**/Gemfile.*.lock
|
5
|
+
|
6
|
+
gemfiles=(
|
7
|
+
"gemfiles/activerecord-5.2/Gemfile.sqlite3"
|
8
|
+
"gemfiles/activerecord-6.0/Gemfile.sqlite3"
|
9
|
+
"gemfiles/activerecord-6.1/Gemfile.sqlite3"
|
10
|
+
"gemfiles/activerecord-7.0/Gemfile.sqlite3"
|
11
|
+
|
12
|
+
"gemfiles/activerecord-5.2/Gemfile.mysql2"
|
13
|
+
"gemfiles/activerecord-6.0/Gemfile.mysql2"
|
14
|
+
"gemfiles/activerecord-6.1/Gemfile.mysql2"
|
15
|
+
"gemfiles/activerecord-7.0/Gemfile.mysql2"
|
16
|
+
|
17
|
+
"gemfiles/activerecord-5.2/Gemfile.postgresql"
|
18
|
+
"gemfiles/activerecord-6.0/Gemfile.postgresql"
|
19
|
+
"gemfiles/activerecord-6.1/Gemfile.postgresql"
|
20
|
+
"gemfiles/activerecord-7.0/Gemfile.postgresql"
|
21
|
+
)
|
22
|
+
|
23
|
+
for gemfile in "${gemfiles[@]}"; do
|
24
|
+
database=$(echo $gemfile | cut -d'.' -f3)
|
25
|
+
|
26
|
+
echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
27
|
+
BUNDLE_GEMFILE=$gemfile bundle install
|
28
|
+
BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
29
|
+
done
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# fix conflicts between different ruby versions
|
4
|
+
rm -rf gemfiles/**/Gemfile.*.lock
|
5
|
+
|
6
|
+
gemfiles=(
|
7
|
+
"gemfiles/activerecord-6.0/Gemfile.sqlite3"
|
8
|
+
"gemfiles/activerecord-6.1/Gemfile.sqlite3"
|
9
|
+
"gemfiles/activerecord-7.0/Gemfile.sqlite3"
|
10
|
+
|
11
|
+
"gemfiles/activerecord-6.0/Gemfile.mysql2"
|
12
|
+
"gemfiles/activerecord-6.1/Gemfile.mysql2"
|
13
|
+
"gemfiles/activerecord-7.0/Gemfile.mysql2"
|
14
|
+
|
15
|
+
"gemfiles/activerecord-6.0/Gemfile.postgresql"
|
16
|
+
"gemfiles/activerecord-6.1/Gemfile.postgresql"
|
17
|
+
"gemfiles/activerecord-7.0/Gemfile.postgresql"
|
18
|
+
)
|
19
|
+
|
20
|
+
for gemfile in "${gemfiles[@]}"; do
|
21
|
+
database=$(echo $gemfile | cut -d'.' -f3)
|
22
|
+
|
23
|
+
echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
24
|
+
BUNDLE_GEMFILE=$gemfile bundle install
|
25
|
+
BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
26
|
+
done
|
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# fix conflicts between different ruby versions
|
4
|
+
rm -rf gemfiles/**/Gemfile.*.lock
|
5
|
+
|
6
|
+
gemfiles=(
|
7
|
+
"gemfiles/activerecord-6.0/Gemfile.sqlite3"
|
8
|
+
"gemfiles/activerecord-6.1/Gemfile.sqlite3"
|
9
|
+
"gemfiles/activerecord-7.0/Gemfile.sqlite3"
|
10
|
+
|
11
|
+
"gemfiles/activerecord-6.0/Gemfile.mysql2"
|
12
|
+
"gemfiles/activerecord-6.1/Gemfile.mysql2"
|
13
|
+
"gemfiles/activerecord-7.0/Gemfile.mysql2"
|
14
|
+
|
15
|
+
"gemfiles/activerecord-6.0/Gemfile.postgresql"
|
16
|
+
"gemfiles/activerecord-6.1/Gemfile.postgresql"
|
17
|
+
"gemfiles/activerecord-7.0/Gemfile.postgresql"
|
18
|
+
)
|
19
|
+
|
20
|
+
for gemfile in "${gemfiles[@]}"; do
|
21
|
+
database=$(echo $gemfile | cut -d'.' -f3)
|
22
|
+
|
23
|
+
echo BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
24
|
+
BUNDLE_GEMFILE=$gemfile bundle install
|
25
|
+
BUNDLE_GEMFILE=$gemfile db=$database bundle exec rake test
|
26
|
+
done
|
data/docker/test-ruby.sh
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
# fix conflicts between different ruby versions
|
4
|
+
rm -rf gemfiles/**/Gemfile.*.lock
|
5
|
+
|
6
|
+
echo BUNDLE_GEMFILE=$BUNDLE_GEMFILE db=$db bundle exec rake test
|
7
|
+
BUNDLE_GEMFILE=$BUNDLE_GEMFILE bundle install
|
8
|
+
BUNDLE_GEMFILE=$BUNDLE_GEMFILE db=$db bundle exec rake test
|
data/docker-compose.yml
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
services:
|
2
|
+
ruby_2_5: &ruby_2_5
|
3
|
+
working_dir: /app
|
4
|
+
build:
|
5
|
+
context: .
|
6
|
+
dockerfile: docker/ruby-2.5/Dockerfile
|
7
|
+
volumes:
|
8
|
+
- .:/app
|
9
|
+
command: bash -c "bundle install && bundle exec rake test"
|
10
|
+
environment:
|
11
|
+
POSTGRESQL_DB_HOST: postgres
|
12
|
+
POSTGRESQL_DB_USER: transaction_retry_continued
|
13
|
+
POSTGRESQL_DB_PASS: database
|
14
|
+
POSTGRESQL_DB_NAME: transaction_isolation_continued_test
|
15
|
+
MYSQL_DB_HOST: mysql
|
16
|
+
MYSQL_DB_USER: root
|
17
|
+
MYSQL_DB_PASS: database
|
18
|
+
MYSQL_DB_NAME: transaction_isolation_continued_test
|
19
|
+
depends_on:
|
20
|
+
- mysql
|
21
|
+
- postgres
|
22
|
+
ruby_2_7:
|
23
|
+
<<: *ruby_2_5
|
24
|
+
build:
|
25
|
+
context: .
|
26
|
+
dockerfile: docker/ruby-2.7/Dockerfile
|
27
|
+
ruby_3_0:
|
28
|
+
<<: *ruby_2_5
|
29
|
+
command: ./docker/test-ruby-3.0.sh
|
30
|
+
build:
|
31
|
+
context: .
|
32
|
+
dockerfile: docker/ruby-3.0/Dockerfile
|
33
|
+
ruby_3_1:
|
34
|
+
<<: *ruby_2_5
|
35
|
+
command: ./docker/test-ruby-3.1.sh
|
36
|
+
build:
|
37
|
+
context: .
|
38
|
+
dockerfile: docker/ruby-3.1/Dockerfile
|
39
|
+
postgres:
|
40
|
+
image: postgres:9.6
|
41
|
+
environment:
|
42
|
+
POSTGRES_USER: transaction_retry_continued
|
43
|
+
POSTGRES_PASSWORD: database
|
44
|
+
POSTGRES_DB: transaction_isolation_continued_test
|
45
|
+
ports:
|
46
|
+
- "5432:5432"
|
47
|
+
healthcheck:
|
48
|
+
test: [ "CMD-SHELL", "pg_isready -q" ]
|
49
|
+
interval: 5s
|
50
|
+
timeout: 5s
|
51
|
+
retries: 5
|
52
|
+
mysql:
|
53
|
+
image: biarms/mysql:5.7
|
54
|
+
environment:
|
55
|
+
MYSQL_ROOT_PASSWORD: database
|
56
|
+
MYSQL_DATABASE: transaction_isolation_continued_test
|
57
|
+
ports:
|
58
|
+
- "3306:3306"
|
59
|
+
healthcheck:
|
60
|
+
test: "mysqladmin ping --host=0.0.0.0 --password=database --silent"
|
data/gemfiles/Gemfile.base
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
gemspec path: Dir.getwd
|
3
3
|
|
4
|
-
gem '
|
4
|
+
gem 'transaction_isolation_continued'
|
5
5
|
|
6
|
-
File.exist?(gemfile_local = File.expand_path('
|
6
|
+
File.exist?(gemfile_local = File.expand_path('../Gemfile.local', __FILE__)) and eval File.read(gemfile_local), binding, gemfile_local
|
7
|
+
|
8
|
+
gem 'simplecov'
|
9
|
+
gem 'simplecov_json_formatter'
|
@@ -1,11 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
1
4
|
require 'active_record/base'
|
2
5
|
|
3
6
|
module TransactionRetry
|
4
7
|
module ActiveRecord
|
5
8
|
module Base
|
6
|
-
|
7
|
-
|
8
|
-
base.extend( ClassMethods )
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
9
11
|
base.class_eval do
|
10
12
|
class << self
|
11
13
|
alias_method :transaction_without_retry, :transaction
|
@@ -13,70 +15,68 @@ module TransactionRetry
|
|
13
15
|
end
|
14
16
|
end
|
15
17
|
end
|
16
|
-
|
18
|
+
|
17
19
|
module ClassMethods
|
18
|
-
|
19
20
|
def transaction_with_retry(**objects, &block)
|
20
21
|
retry_count = 0
|
21
22
|
|
22
23
|
opts = if objects.is_a? Hash
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
objects
|
25
|
+
else
|
26
|
+
{}
|
27
|
+
end
|
27
28
|
|
28
29
|
retry_on = opts.delete(:retry_on)
|
29
30
|
max_retries = opts.delete(:max_retries) || TransactionRetry.max_retries
|
30
31
|
|
31
32
|
begin
|
32
33
|
transaction_without_retry(**objects, &block)
|
33
|
-
rescue
|
34
|
+
rescue ::ActiveRecord::TransactionIsolationConflict, *retry_on
|
34
35
|
raise if retry_count >= max_retries
|
35
36
|
raise if tr_in_nested_transaction?
|
36
|
-
|
37
|
+
|
37
38
|
retry_count += 1
|
38
39
|
postfix = { 1 => 'st', 2 => 'nd', 3 => 'rd' }[retry_count] || 'th'
|
39
40
|
|
40
|
-
type_s = case
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
type_s = case $ERROR_INFO
|
42
|
+
when ::ActiveRecord::TransactionIsolationConflict
|
43
|
+
'Transaction isolation conflict'
|
44
|
+
else
|
45
|
+
$ERROR_INFO.class.name
|
46
|
+
end
|
46
47
|
|
47
|
-
logger
|
48
|
-
tr_exponential_pause(
|
48
|
+
logger&.warn "#{type_s} detected. Retrying for the #{retry_count}-#{postfix} time..."
|
49
|
+
tr_exponential_pause(retry_count)
|
49
50
|
retry
|
50
51
|
end
|
51
52
|
end
|
52
|
-
|
53
|
-
private
|
54
53
|
|
55
|
-
|
56
|
-
# Cap the sleep time at 32 seconds.
|
57
|
-
# An ugly tr_ prefix is used to minimize the risk of method clash in the future.
|
58
|
-
def tr_exponential_pause( count )
|
59
|
-
seconds = TransactionRetry.wait_times[count-1] || 32
|
54
|
+
private
|
60
55
|
|
61
|
-
|
62
|
-
|
56
|
+
# Sleep 0, 1, 2, 4, ... seconds up to the TransactionRetry.max_retries.
|
57
|
+
# Cap the sleep time at 32 seconds.
|
58
|
+
# An ugly tr_ prefix is used to minimize the risk of method clash in the future.
|
59
|
+
def tr_exponential_pause(count)
|
60
|
+
seconds = TransactionRetry.wait_times[count - 1] || 32
|
63
61
|
|
64
|
-
|
65
|
-
|
62
|
+
if TransactionRetry.fuzz
|
63
|
+
fuzz_factor = [seconds * 0.25, 1].max
|
66
64
|
|
67
|
-
|
68
|
-
end
|
69
|
-
|
70
|
-
# Returns true if we are in the nested transaction (the one with :requires_new => true).
|
71
|
-
# Returns false otherwise.
|
72
|
-
# An ugly tr_ prefix is used to minimize the risk of method clash in the future.
|
73
|
-
def tr_in_nested_transaction?
|
74
|
-
connection.open_transactions != 0
|
65
|
+
seconds += rand * (fuzz_factor * 2) - fuzz_factor
|
75
66
|
end
|
76
67
|
|
68
|
+
sleep(seconds) if seconds.positive?
|
69
|
+
end
|
70
|
+
|
71
|
+
# Returns true if we are in the nested transaction (the one with :requires_new => true).
|
72
|
+
# Returns false otherwise.
|
73
|
+
# An ugly tr_ prefix is used to minimize the risk of method clash in the future.
|
74
|
+
def tr_in_nested_transaction?
|
75
|
+
connection.open_transactions != 0
|
76
|
+
end
|
77
77
|
end
|
78
78
|
end
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
-
ActiveRecord::Base.
|
82
|
+
ActiveRecord::Base.include TransactionRetry::ActiveRecord::Base
|
data/lib/transaction_retry.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
|
-
|
2
|
-
require "transaction_isolation"
|
1
|
+
# frozen_string_literal: true
|
3
2
|
|
4
|
-
|
3
|
+
require 'active_record'
|
4
|
+
require 'transaction_isolation'
|
5
5
|
|
6
|
-
|
6
|
+
require_relative 'transaction_retry/version'
|
7
7
|
|
8
|
+
module TransactionRetry
|
8
9
|
# Must be called after ActiveRecord established a connection.
|
9
10
|
# Only then we know which connection adapter is actually loaded and can be enhanced.
|
10
11
|
# Please note ActiveRecord does not load unused adapters.
|
@@ -13,7 +14,7 @@ module TransactionRetry
|
|
13
14
|
require_relative 'transaction_retry/active_record/base'
|
14
15
|
end
|
15
16
|
|
16
|
-
if defined?(
|
17
|
+
if defined?(::Rails)
|
17
18
|
# Setup applying the patch after Rails is initialized.
|
18
19
|
class Railtie < ::Rails::Railtie
|
19
20
|
config.after_initialize do
|
@@ -26,7 +27,7 @@ module TransactionRetry
|
|
26
27
|
@@max_retries ||= 3
|
27
28
|
end
|
28
29
|
|
29
|
-
def self.max_retries=(
|
30
|
+
def self.max_retries=(n)
|
30
31
|
@@max_retries = n
|
31
32
|
end
|
32
33
|
|
@@ -34,7 +35,7 @@ module TransactionRetry
|
|
34
35
|
@@wait_times ||= [0, 1, 2, 4, 8, 16, 32]
|
35
36
|
end
|
36
37
|
|
37
|
-
def self.wait_times=(
|
38
|
+
def self.wait_times=(array_of_seconds)
|
38
39
|
@@wait_times = array_of_seconds
|
39
40
|
end
|
40
41
|
|
@@ -42,8 +43,7 @@ module TransactionRetry
|
|
42
43
|
@@fuzz ||= true
|
43
44
|
end
|
44
45
|
|
45
|
-
def self.fuzz=(
|
46
|
+
def self.fuzz=(val)
|
46
47
|
@@fuzz = val
|
47
48
|
end
|
48
|
-
|
49
49
|
end
|
data/test/db/all.rb
CHANGED
data/test/db/db.rb
CHANGED
@@ -1,37 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'fileutils'
|
2
4
|
|
3
5
|
module TransactionRetry
|
4
6
|
module Test
|
5
7
|
module Db
|
6
|
-
|
7
8
|
def self.connect_to_mysql2
|
8
9
|
::ActiveRecord::Base.establish_connection(
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
10
|
+
adapter: 'mysql2',
|
11
|
+
database: ENV['MYSQL_DB_NAME'],
|
12
|
+
host: ENV['MYSQL_DB_HOST'],
|
13
|
+
user: 'root',
|
14
|
+
password: ENV['MYSQL_DB_PASS']
|
14
15
|
)
|
15
16
|
end
|
16
17
|
|
17
18
|
def self.connect_to_postgresql
|
18
19
|
::ActiveRecord::Base.establish_connection(
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
20
|
+
adapter: 'postgresql',
|
21
|
+
database: ENV['POSTGRESQL_DB_NAME'],
|
22
|
+
host: ENV['POSTGRESQL_DB_HOST'],
|
23
|
+
user: ENV['POSTGRESQL_DB_USER'],
|
24
|
+
password: ENV['POSTGRESQL_DB_PASS']
|
24
25
|
)
|
25
26
|
end
|
26
27
|
|
27
28
|
def self.connect_to_sqlite3
|
28
29
|
ActiveRecord::Base.establish_connection(
|
29
|
-
:
|
30
|
-
:
|
31
|
-
:
|
30
|
+
adapter: 'sqlite3',
|
31
|
+
database: ':memory:',
|
32
|
+
verbosity: 'silent'
|
32
33
|
)
|
33
34
|
end
|
34
|
-
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
data/test/db/migrations.rb
CHANGED
@@ -1,20 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module TransactionRetry
|
2
4
|
module Test
|
3
5
|
module Migrations
|
4
|
-
|
5
6
|
def self.run!
|
6
7
|
c = ::ActiveRecord::Base.connection
|
7
8
|
|
8
9
|
# Queued Jobs
|
9
|
-
|
10
|
-
c.create_table
|
11
|
-
t.text
|
12
|
-
t.integer
|
10
|
+
|
11
|
+
c.create_table 'queued_jobs', force: true do |t|
|
12
|
+
t.text 'job', null: false
|
13
|
+
t.integer 'status', default: 0, null: false
|
13
14
|
t.timestamps
|
14
15
|
end
|
15
|
-
|
16
16
|
end
|
17
|
-
|
18
17
|
end
|
19
18
|
end
|
20
19
|
end
|
data/test/db/queued_job.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'test_helper'
|
4
4
|
|
@@ -16,22 +16,22 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
|
|
16
16
|
TransactionRetry.wait_times = @original_wait_times
|
17
17
|
QueuedJob.delete_all
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def test_does_not_break_transaction
|
21
21
|
ActiveRecord::Base.transaction do
|
22
|
-
QueuedJob.create!(
|
23
|
-
assert_equal(
|
22
|
+
QueuedJob.create!(job: 'is fun!')
|
23
|
+
assert_equal(1, QueuedJob.count)
|
24
24
|
end
|
25
|
-
assert_equal(
|
25
|
+
assert_equal(1, QueuedJob.count)
|
26
26
|
QueuedJob.first.destroy
|
27
27
|
end
|
28
28
|
|
29
29
|
def test_does_not_break_transaction_rollback
|
30
30
|
ActiveRecord::Base.transaction do
|
31
|
-
QueuedJob.create!(
|
31
|
+
QueuedJob.create!(job: 'gives money!')
|
32
32
|
raise ActiveRecord::Rollback
|
33
33
|
end
|
34
|
-
assert_equal(
|
34
|
+
assert_equal(0, QueuedJob.count)
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_retries_transaction_on_transaction_isolation_conflict
|
@@ -40,30 +40,29 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
|
|
40
40
|
ActiveRecord::Base.transaction do
|
41
41
|
if first_run
|
42
42
|
first_run = false
|
43
|
-
message =
|
44
|
-
raise ActiveRecord::TransactionIsolationConflict
|
43
|
+
message = 'Deadlock found when trying to get lock'
|
44
|
+
raise ActiveRecord::TransactionIsolationConflict, message
|
45
45
|
end
|
46
|
-
QueuedJob.create!(
|
46
|
+
QueuedJob.create!(job: 'is cool!')
|
47
47
|
end
|
48
|
-
assert_equal(
|
49
|
-
|
48
|
+
assert_equal(1, QueuedJob.count)
|
49
|
+
|
50
50
|
QueuedJob.first.destroy
|
51
51
|
end
|
52
52
|
|
53
53
|
def test_does_not_retry_on_unknown_error
|
54
54
|
first_run = true
|
55
55
|
|
56
|
-
assert_raises(
|
56
|
+
assert_raises(CustomError) do
|
57
57
|
ActiveRecord::Base.transaction do
|
58
58
|
if first_run
|
59
59
|
first_run = false
|
60
|
-
|
61
|
-
raise CustomError, "random error"
|
60
|
+
raise CustomError, 'random error'
|
62
61
|
end
|
63
|
-
QueuedJob.create!(
|
62
|
+
QueuedJob.create!(job: 'is cool!')
|
64
63
|
end
|
65
64
|
end
|
66
|
-
assert_equal(
|
65
|
+
assert_equal(0, QueuedJob.count)
|
67
66
|
end
|
68
67
|
|
69
68
|
def test_retries_on_custom_error
|
@@ -72,13 +71,12 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
|
|
72
71
|
ActiveRecord::Base.transaction(retry_on: CustomError) do
|
73
72
|
if first_run
|
74
73
|
first_run = false
|
75
|
-
|
76
|
-
raise CustomError, "random error"
|
74
|
+
raise CustomError, 'random error'
|
77
75
|
end
|
78
|
-
QueuedJob.create!(
|
76
|
+
QueuedJob.create!(job: 'is cool!')
|
79
77
|
end
|
80
|
-
assert_equal(
|
81
|
-
|
78
|
+
assert_equal(1, QueuedJob.count)
|
79
|
+
|
82
80
|
QueuedJob.first.destroy
|
83
81
|
end
|
84
82
|
|
@@ -86,50 +84,47 @@ class TransactionWithRetryTest < MiniTest::Unit::TestCase
|
|
86
84
|
TransactionRetry.max_retries = 1
|
87
85
|
run = 0
|
88
86
|
|
89
|
-
assert_raises(
|
87
|
+
assert_raises(ActiveRecord::TransactionIsolationConflict) do
|
90
88
|
ActiveRecord::Base.transaction do
|
91
89
|
run += 1
|
92
|
-
message =
|
93
|
-
raise ActiveRecord::TransactionIsolationConflict
|
90
|
+
message = 'Deadlock found when trying to get lock'
|
91
|
+
raise ActiveRecord::TransactionIsolationConflict, message
|
94
92
|
end
|
95
93
|
end
|
96
|
-
|
97
|
-
assert_equal(
|
94
|
+
|
95
|
+
assert_equal(2, run) # normal run + one retry
|
98
96
|
|
99
97
|
TransactionRetry.max_retries = 3
|
100
98
|
|
101
99
|
run = 0
|
102
100
|
|
103
|
-
assert_raises(
|
101
|
+
assert_raises(ActiveRecord::TransactionIsolationConflict) do
|
104
102
|
ActiveRecord::Base.transaction(max_retries: 1) do
|
105
103
|
run += 1
|
106
|
-
message =
|
107
|
-
raise ActiveRecord::TransactionIsolationConflict
|
104
|
+
message = 'Deadlock found when trying to get lock'
|
105
|
+
raise ActiveRecord::TransactionIsolationConflict, message
|
108
106
|
end
|
109
107
|
end
|
110
|
-
|
111
|
-
assert_equal(
|
108
|
+
|
109
|
+
assert_equal(2, run) # normal run + one retry
|
112
110
|
end
|
113
111
|
|
114
112
|
def test_does_not_retry_nested_transaction
|
115
113
|
first_try = true
|
116
114
|
|
117
115
|
ActiveRecord::Base.transaction do
|
118
|
-
|
119
|
-
|
120
|
-
ActiveRecord::Base.transaction( :requires_new => true ) do
|
116
|
+
assert_raises(ActiveRecord::TransactionIsolationConflict) do
|
117
|
+
ActiveRecord::Base.transaction(requires_new: true) do
|
121
118
|
if first_try
|
122
119
|
first_try = false
|
123
|
-
message =
|
124
|
-
raise ActiveRecord::TransactionIsolationConflict
|
120
|
+
message = 'Deadlock found when trying to get lock'
|
121
|
+
raise ActiveRecord::TransactionIsolationConflict, message
|
125
122
|
end
|
126
|
-
QueuedJob.create!(
|
123
|
+
QueuedJob.create!(job: 'is cool!')
|
127
124
|
end
|
128
125
|
end
|
129
|
-
|
130
126
|
end
|
131
|
-
|
132
|
-
assert_equal( 0, QueuedJob.count )
|
133
|
-
end
|
134
127
|
|
128
|
+
assert_equal(0, QueuedJob.count)
|
129
|
+
end
|
135
130
|
end
|
data/test/library_setup.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Prepares application to be tested (requires files, connects to db, resets schema and data, applies patches, etc.)
|
2
4
|
|
3
5
|
# Initialize database
|
4
6
|
require 'db/all'
|
5
7
|
|
6
8
|
case ENV['db']
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
9
|
+
when 'mysql2'
|
10
|
+
TransactionRetry::Test::Db.connect_to_mysql2
|
11
|
+
when 'postgresql'
|
12
|
+
TransactionRetry::Test::Db.connect_to_postgresql
|
13
|
+
when 'sqlite3'
|
14
|
+
TransactionRetry::Test::Db.connect_to_sqlite3
|
15
|
+
else
|
16
|
+
TransactionRetry::Test::Db.connect_to_mysql2
|
15
17
|
end
|
16
18
|
|
17
19
|
require 'logger'
|
18
|
-
ActiveRecord::Base.logger = Logger.new(
|
20
|
+
ActiveRecord::Base.logger = Logger.new(File.expand_path("#{File.dirname(__FILE__)}/log/test.log"))
|
19
21
|
|
20
22
|
TransactionRetry::Test::Migrations.run!
|
21
23
|
|
data/test/test_helper.rb
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'simplecov'
|
4
|
+
require 'simplecov_json_formatter'
|
5
|
+
SimpleCov.formatter = SimpleCov::Formatter::JSONFormatter
|
6
|
+
|
7
|
+
SimpleCov.start do
|
8
|
+
add_filter '/test/'
|
9
|
+
end
|
7
10
|
|
8
11
|
# Load and initialize the application to be tested
|
9
12
|
require 'library_setup'
|
data/test/test_runner.rb
CHANGED
@@ -1,23 +1,24 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
4
|
+
require 'transaction_retry/version'
|
4
5
|
|
5
6
|
Gem::Specification.new do |s|
|
6
|
-
s.name =
|
7
|
+
s.name = 'transaction_retry_continued'
|
7
8
|
s.version = TransactionRetry::VERSION
|
8
|
-
s.authors = [
|
9
|
-
s.homepage =
|
10
|
-
s.summary =
|
11
|
-
s.description =
|
9
|
+
s.authors = ['Iago Pimenta']
|
10
|
+
s.homepage = 'https://github.com/iagopiimenta/transaction_retry_continued'
|
11
|
+
s.summary = 'Retries database transaction on deadlock and transaction serialization errors. Supports MySQL, PostgreSQL and SQLite.'
|
12
|
+
s.description = 'Retries database transaction on deadlock and transaction serialization errors. Supports MySQL, PostgreSQL and SQLite.'
|
12
13
|
s.required_ruby_version = '>= 2.5'
|
13
14
|
|
14
15
|
s.files = `git ls-files`.split("\n")
|
15
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
16
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
-
s.require_paths = [
|
17
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
18
|
+
s.require_paths = ['lib']
|
18
19
|
|
19
|
-
s.add_development_dependency 'rake', '~> 13.0'
|
20
20
|
s.add_development_dependency 'minitest', '5.3.4'
|
21
|
-
s.
|
22
|
-
s.add_runtime_dependency
|
23
|
-
|
21
|
+
s.add_development_dependency 'rake', '~> 13.0'
|
22
|
+
s.add_runtime_dependency 'activerecord', '>= 5.2'
|
23
|
+
s.add_runtime_dependency 'transaction_isolation_continued', '>= 1.0'
|
24
|
+
end
|
metadata
CHANGED
@@ -1,43 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: transaction_retry_continued
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Iago Pimenta
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-06-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: minitest
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 5.3.4
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version:
|
26
|
+
version: 5.3.4
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '13.0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '13.0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: activerecord
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -79,7 +79,16 @@ files:
|
|
79
79
|
- LICENSE
|
80
80
|
- README.md
|
81
81
|
- Rakefile
|
82
|
-
-
|
82
|
+
- docker-compose.yml
|
83
|
+
- docker/ruby-2.5/Dockerfile
|
84
|
+
- docker/ruby-2.7/Dockerfile
|
85
|
+
- docker/ruby-3.0/Dockerfile
|
86
|
+
- docker/ruby-3.1/Dockerfile
|
87
|
+
- docker/test-ruby-2.5.sh
|
88
|
+
- docker/test-ruby-2.7.sh
|
89
|
+
- docker/test-ruby-3.0.sh
|
90
|
+
- docker/test-ruby-3.1.sh
|
91
|
+
- docker/test-ruby.sh
|
83
92
|
- gemfiles/Gemfile.base
|
84
93
|
- gemfiles/activerecord-5.2/Gemfile.base
|
85
94
|
- gemfiles/activerecord-5.2/Gemfile.mysql2
|
@@ -108,10 +117,8 @@ files:
|
|
108
117
|
- test/integration/active_record/base/transaction_with_retry_test.rb
|
109
118
|
- test/library_setup.rb
|
110
119
|
- test/log/.gitkeep
|
111
|
-
- test/test_console.rb
|
112
120
|
- test/test_helper.rb
|
113
121
|
- test/test_runner.rb
|
114
|
-
- tests
|
115
122
|
- transaction_retry_continued.gemspec
|
116
123
|
homepage: https://github.com/iagopiimenta/transaction_retry_continued
|
117
124
|
licenses: []
|
@@ -144,6 +151,5 @@ test_files:
|
|
144
151
|
- test/integration/active_record/base/transaction_with_retry_test.rb
|
145
152
|
- test/library_setup.rb
|
146
153
|
- test/log/.gitkeep
|
147
|
-
- test/test_console.rb
|
148
154
|
- test/test_helper.rb
|
149
155
|
- test/test_runner.rb
|
data/test/test_console.rb
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
# Ensure that LOAD_PATH is the same as when running "rake test"; normally rake takes care of that
|
2
|
-
$LOAD_PATH << File.expand_path( ".", File.dirname( __FILE__ ) )
|
3
|
-
$LOAD_PATH << File.expand_path( "./lib", File.dirname( __FILE__ ) )
|
4
|
-
$LOAD_PATH << File.expand_path( "./test", File.dirname( __FILE__ ) )
|
5
|
-
|
6
|
-
# Boot the app
|
7
|
-
require_relative 'library_setup'
|
8
|
-
|
9
|
-
# Fire the console
|
10
|
-
require 'pry'
|
11
|
-
binding.pry
|