activerecord-rescue_from_duplicate 0.2.6 → 0.2.7
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/dependabot.yml +6 -0
- data/.github/workflows/ci.yml +4 -3
- data/.github/workflows/release.yml +26 -0
- data/.gitignore +0 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +100 -0
- data/activerecord-rescue_from_duplicate.gemspec +2 -0
- data/lib/rescue_from_duplicate/active_record/extension.rb +10 -1
- data/lib/rescue_from_duplicate/active_record/version.rb +1 -1
- data/spec/other_exception_columns_spec.rb +60 -0
- metadata +14 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00a9576198c85da3503fc44c7420dd287082a3a8219fe716cd88340ab8da432d
|
|
4
|
+
data.tar.gz: 196bb9b82c0d333e4fdbaf254992f9e2cf997d62d171420bff1859bdf4d1aca8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: df70343a3219b0c4c2ce0497329d3f59ac3c1e60b6888fabb8b1b5a0562981d1a1b0a27ad1eb0f52e7b190dbfb7584cd6dbbcf72df9f4672b8d4de34f04807cd
|
|
7
|
+
data.tar.gz: fb404a3201ab1bc5739a4ac71686d7bb5002ce849e439c87063aa9f530f9390165e9044a549bcc733fcc15d7d004c4dd5b1386b1b8a203be4762c5f94bb03c9d
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -24,15 +24,16 @@ jobs:
|
|
|
24
24
|
MYSQL_DATABASE: "rescue_from_duplicate"
|
|
25
25
|
ports:
|
|
26
26
|
- "3306:3306"
|
|
27
|
+
options: --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 10
|
|
27
28
|
strategy:
|
|
28
29
|
fail-fast: false
|
|
29
30
|
matrix:
|
|
30
|
-
ruby: ["3.2"]
|
|
31
|
+
ruby: ["3.0", "3.1", "3.2", "3.3"]
|
|
31
32
|
steps:
|
|
32
33
|
- name: Check out code
|
|
33
|
-
uses: actions/checkout@
|
|
34
|
+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
|
34
35
|
- name: Set up Ruby ${{ matrix.ruby }}
|
|
35
|
-
uses: ruby/setup-ruby@v1
|
|
36
|
+
uses: ruby/setup-ruby@e34163cd15f4bb403dcd72d98e295997e6a55798 # v1.238.0
|
|
36
37
|
with:
|
|
37
38
|
ruby-version: ${{ matrix.ruby }}
|
|
38
39
|
bundler-cache: true
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on: workflow_dispatch
|
|
4
|
+
|
|
5
|
+
jobs:
|
|
6
|
+
release:
|
|
7
|
+
permissions:
|
|
8
|
+
contents: write
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
environment: release
|
|
12
|
+
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
17
|
+
- name: Install required packages
|
|
18
|
+
run: |
|
|
19
|
+
sudo apt-get update
|
|
20
|
+
sudo apt-get -y install libmemcached-dev libmysqlclient-dev libpq-dev libsasl2-dev
|
|
21
|
+
- name: Set up Ruby
|
|
22
|
+
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
|
|
23
|
+
with:
|
|
24
|
+
bundler-cache: true
|
|
25
|
+
ruby-version: 3.3
|
|
26
|
+
- uses: rubygems/release-gem@052cc82692552de3ef2b81fd670e41d13cba8092 # v1.4.0
|
data/.gitignore
CHANGED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.3.0
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.2.7] - 2026-08-26
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- It was possible that the gem wouldn't rescue a unique constraint violation error in
|
|
15
|
+
the even that a non unique index name would match a subset of the unique index
|
|
16
|
+
to rescue.
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
activerecord-rescue_from_duplicate (0.2.7)
|
|
5
|
+
activerecord (>= 7)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
activemodel (7.1.5.2)
|
|
11
|
+
activesupport (= 7.1.5.2)
|
|
12
|
+
activerecord (7.1.5.2)
|
|
13
|
+
activemodel (= 7.1.5.2)
|
|
14
|
+
activesupport (= 7.1.5.2)
|
|
15
|
+
timeout (>= 0.4.0)
|
|
16
|
+
activesupport (7.1.5.2)
|
|
17
|
+
base64
|
|
18
|
+
benchmark (>= 0.3)
|
|
19
|
+
bigdecimal
|
|
20
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
|
21
|
+
connection_pool (>= 2.2.5)
|
|
22
|
+
drb
|
|
23
|
+
i18n (>= 1.6, < 2)
|
|
24
|
+
logger (>= 1.4.2)
|
|
25
|
+
minitest (>= 5.1)
|
|
26
|
+
mutex_m
|
|
27
|
+
securerandom (>= 0.3)
|
|
28
|
+
tzinfo (~> 2.0)
|
|
29
|
+
base64 (0.3.0)
|
|
30
|
+
benchmark (0.4.1)
|
|
31
|
+
bigdecimal (3.2.2)
|
|
32
|
+
coderay (1.1.3)
|
|
33
|
+
concurrent-ruby (1.3.5)
|
|
34
|
+
connection_pool (2.5.3)
|
|
35
|
+
diff-lcs (1.5.1)
|
|
36
|
+
docile (1.4.0)
|
|
37
|
+
drb (2.2.3)
|
|
38
|
+
i18n (1.14.7)
|
|
39
|
+
concurrent-ruby (~> 1.0)
|
|
40
|
+
logger (1.7.0)
|
|
41
|
+
method_source (1.0.0)
|
|
42
|
+
minitest (5.25.5)
|
|
43
|
+
mutex_m (0.3.0)
|
|
44
|
+
mysql2 (0.5.6)
|
|
45
|
+
pg (1.5.6)
|
|
46
|
+
pry (0.14.2)
|
|
47
|
+
coderay (~> 1.1)
|
|
48
|
+
method_source (~> 1.0)
|
|
49
|
+
rake (13.2.0)
|
|
50
|
+
rspec (3.13.0)
|
|
51
|
+
rspec-core (~> 3.13.0)
|
|
52
|
+
rspec-expectations (~> 3.13.0)
|
|
53
|
+
rspec-mocks (~> 3.13.0)
|
|
54
|
+
rspec-core (3.13.0)
|
|
55
|
+
rspec-support (~> 3.13.0)
|
|
56
|
+
rspec-expectations (3.13.0)
|
|
57
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
58
|
+
rspec-support (~> 3.13.0)
|
|
59
|
+
rspec-mocks (3.13.0)
|
|
60
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
61
|
+
rspec-support (~> 3.13.0)
|
|
62
|
+
rspec-support (3.13.1)
|
|
63
|
+
securerandom (0.3.2)
|
|
64
|
+
simplecov (0.22.0)
|
|
65
|
+
docile (~> 1.1)
|
|
66
|
+
simplecov-html (~> 0.11)
|
|
67
|
+
simplecov_json_formatter (~> 0.1)
|
|
68
|
+
simplecov-html (0.12.3)
|
|
69
|
+
simplecov_json_formatter (0.1.4)
|
|
70
|
+
sqlite3 (1.7.3-aarch64-linux)
|
|
71
|
+
sqlite3 (1.7.3-arm-linux)
|
|
72
|
+
sqlite3 (1.7.3-arm64-darwin)
|
|
73
|
+
sqlite3 (1.7.3-x86-linux)
|
|
74
|
+
sqlite3 (1.7.3-x86_64-darwin)
|
|
75
|
+
sqlite3 (1.7.3-x86_64-linux)
|
|
76
|
+
timeout (0.4.3)
|
|
77
|
+
tzinfo (2.0.6)
|
|
78
|
+
concurrent-ruby (~> 1.0)
|
|
79
|
+
|
|
80
|
+
PLATFORMS
|
|
81
|
+
aarch64-linux
|
|
82
|
+
arm-linux
|
|
83
|
+
arm64-darwin
|
|
84
|
+
x86-linux
|
|
85
|
+
x86_64-darwin
|
|
86
|
+
x86_64-linux
|
|
87
|
+
|
|
88
|
+
DEPENDENCIES
|
|
89
|
+
activerecord-rescue_from_duplicate!
|
|
90
|
+
bundler
|
|
91
|
+
mysql2
|
|
92
|
+
pg
|
|
93
|
+
pry
|
|
94
|
+
rake
|
|
95
|
+
rspec
|
|
96
|
+
simplecov
|
|
97
|
+
sqlite3
|
|
98
|
+
|
|
99
|
+
BUNDLED WITH
|
|
100
|
+
2.5.3
|
|
@@ -13,6 +13,8 @@ Gem::Specification.new do |spec|
|
|
|
13
13
|
spec.homepage = "https://github.com/Shopify/activerecord-rescue_from_duplicate"
|
|
14
14
|
spec.license = "MIT"
|
|
15
15
|
|
|
16
|
+
spec.required_ruby_version = ">= 3.0"
|
|
17
|
+
|
|
16
18
|
spec.files = `git ls-files`.split($/)
|
|
17
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
20
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
@@ -2,6 +2,8 @@ require 'active_support/core_ext/class'
|
|
|
2
2
|
|
|
3
3
|
module RescueFromDuplicate::ActiveRecord
|
|
4
4
|
module Extension
|
|
5
|
+
DUPLICATE_KEY_REGEX = /for key ['"](?<key>[^'"]+)['"]/
|
|
6
|
+
|
|
5
7
|
extend ActiveSupport::Concern
|
|
6
8
|
|
|
7
9
|
module ClassMethods
|
|
@@ -80,8 +82,15 @@ module RescueFromDuplicate::ActiveRecord
|
|
|
80
82
|
end
|
|
81
83
|
|
|
82
84
|
def other_exception_columns(exception)
|
|
85
|
+
key = exception.message[DUPLICATE_KEY_REGEX, :key]
|
|
86
|
+
return [] unless key
|
|
87
|
+
|
|
88
|
+
# Mysql > 8.0 prefixes indexes with the table name.
|
|
89
|
+
index_name = key.delete_prefix("#{self.class.table_name}.")
|
|
90
|
+
|
|
83
91
|
indexes = self.class.connection.schema_cache.indexes(self.class.table_name)
|
|
84
|
-
indexes.
|
|
92
|
+
index = indexes.find { |i| i.name == index_name }
|
|
93
|
+
index ? index.columns : []
|
|
85
94
|
end
|
|
86
95
|
end
|
|
87
96
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module RescueFromDuplicate
|
|
4
|
+
describe "#other_exception_columns (MySQL/MariaDB duplicate-key parsing)" do
|
|
5
|
+
let(:prefix_index) do
|
|
6
|
+
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(
|
|
7
|
+
"order_transactions", "index_order_transactions_on_shop_id", false, ["shop_id"]
|
|
8
|
+
)
|
|
9
|
+
end
|
|
10
|
+
let(:unique_index) do
|
|
11
|
+
::ActiveRecord::ConnectionAdapters::IndexDefinition.new(
|
|
12
|
+
"order_transactions", "index_order_transactions_on_shop_id_and_unique_token", true, ["shop_id", "unique_token"]
|
|
13
|
+
)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
let(:model) do
|
|
17
|
+
indexes = [prefix_index, unique_index]
|
|
18
|
+
connection = double(schema_cache: double(indexes: indexes))
|
|
19
|
+
Class.new do
|
|
20
|
+
include RescueFromDuplicate::ActiveRecord::Extension
|
|
21
|
+
define_singleton_method(:table_name) { "order_transactions" }
|
|
22
|
+
define_singleton_method(:connection) { connection }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
subject(:columns) { model.allocate.send(:other_exception_columns, exception) }
|
|
27
|
+
|
|
28
|
+
context "with a MySQL 8.0+ table-qualified key name" do
|
|
29
|
+
let(:exception) do
|
|
30
|
+
::ActiveRecord::RecordNotUnique.new(
|
|
31
|
+
"Duplicate entry '1-abc' for key 'order_transactions.index_order_transactions_on_shop_id_and_unique_token'"
|
|
32
|
+
)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it "selects the violated index regardless of ordering" do
|
|
36
|
+
expect(columns).to eq ["shop_id", "unique_token"]
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
context "with a MySQL 5.7 / MariaDB bare key name" do
|
|
41
|
+
let(:exception) do
|
|
42
|
+
::ActiveRecord::RecordNotUnique.new(
|
|
43
|
+
"Duplicate entry '1-abc' for key 'index_order_transactions_on_shop_id_and_unique_token'"
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "selects the violated index by exact name" do
|
|
48
|
+
expect(columns).to eq ["shop_id", "unique_token"]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "when the message carries no key name" do
|
|
53
|
+
let(:exception) { ::ActiveRecord::RecordNotUnique.new("some unrelated error") }
|
|
54
|
+
|
|
55
|
+
it "returns no columns" do
|
|
56
|
+
expect(columns).to eq []
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activerecord-rescue_from_duplicate
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Guillaume Malette
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-08-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activerecord
|
|
@@ -143,11 +143,16 @@ executables: []
|
|
|
143
143
|
extensions: []
|
|
144
144
|
extra_rdoc_files: []
|
|
145
145
|
files:
|
|
146
|
+
- ".github/dependabot.yml"
|
|
146
147
|
- ".github/workflows/ci.yml"
|
|
147
148
|
- ".github/workflows/cla.yml"
|
|
149
|
+
- ".github/workflows/release.yml"
|
|
148
150
|
- ".gitignore"
|
|
149
151
|
- ".rspec"
|
|
152
|
+
- ".ruby-version"
|
|
153
|
+
- CHANGELOG.md
|
|
150
154
|
- Gemfile
|
|
155
|
+
- Gemfile.lock
|
|
151
156
|
- LICENSE.txt
|
|
152
157
|
- README.md
|
|
153
158
|
- Rakefile
|
|
@@ -161,6 +166,7 @@ files:
|
|
|
161
166
|
- lib/rescue_from_duplicate/rescuer.rb
|
|
162
167
|
- lib/rescue_from_duplicate/uniqueness_rescuer.rb
|
|
163
168
|
- shipit.rubygems.yml
|
|
169
|
+
- spec/other_exception_columns_spec.rb
|
|
164
170
|
- spec/rescue_from_duplicate_spec.rb
|
|
165
171
|
- spec/rescuer_spec.rb
|
|
166
172
|
- spec/spec_helper.rb
|
|
@@ -172,7 +178,7 @@ licenses:
|
|
|
172
178
|
- MIT
|
|
173
179
|
metadata:
|
|
174
180
|
allowed_push_host: https://rubygems.org
|
|
175
|
-
post_install_message:
|
|
181
|
+
post_install_message:
|
|
176
182
|
rdoc_options: []
|
|
177
183
|
require_paths:
|
|
178
184
|
- lib
|
|
@@ -180,19 +186,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
180
186
|
requirements:
|
|
181
187
|
- - ">="
|
|
182
188
|
- !ruby/object:Gem::Version
|
|
183
|
-
version: '0'
|
|
189
|
+
version: '3.0'
|
|
184
190
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
185
191
|
requirements:
|
|
186
192
|
- - ">="
|
|
187
193
|
- !ruby/object:Gem::Version
|
|
188
194
|
version: '0'
|
|
189
195
|
requirements: []
|
|
190
|
-
rubygems_version: 3.
|
|
191
|
-
signing_key:
|
|
196
|
+
rubygems_version: 3.5.22
|
|
197
|
+
signing_key:
|
|
192
198
|
specification_version: 4
|
|
193
199
|
summary: Rescue from MySQL and Sqlite duplicate errors when trying to insert records
|
|
194
200
|
that fail uniqueness validation
|
|
195
201
|
test_files:
|
|
202
|
+
- spec/other_exception_columns_spec.rb
|
|
196
203
|
- spec/rescue_from_duplicate_spec.rb
|
|
197
204
|
- spec/rescuer_spec.rb
|
|
198
205
|
- spec/spec_helper.rb
|