rescue_unique_constraint-cadetstar 1.6.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 133829749c4c9dd2bcc20628d6544147b7742b4aa389a3569457965f8ad87211
4
+ data.tar.gz: 1c2f5b4eb84e18e0c44a49680678d0f25b70c807b55b40349c430018bf8f0c27
5
+ SHA512:
6
+ metadata.gz: 87bafb69b49d6265a0bb3e7fe84d7bcc44e546a08e4a9c380a55b60bc2686cb0e892e3a4f2cee62f259648bf3f25f85bd0878a11508374d779aa824ec17b77bc
7
+ data.tar.gz: 822ba822132016b8a343957d65a6d94d2d2ff8cd9e3e3585fc67a4abd3e06a66fa3d20374292bbfd64a6aa189b9cfb58b7591c7acc32286d50128042bc747837
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .tags
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
data/.rubocop.yml ADDED
@@ -0,0 +1,2 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.1
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.5.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rescue_unique_constraint.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2015 Reverb.com, LLC
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # RescueUniqueConstraint
2
+
3
+ ActiveRecord doesn't do a great job of rescuing ActiveRecord::RecordNotUnique
4
+ violations resulting from a duplicate entry on a database level unique constraint.
5
+
6
+ This gem automatically rescues the error and instead adds a validation error
7
+ on the field in question, making it behave as if you had a normal uniqueness
8
+ validation.
9
+
10
+ Note that if you have only a unique constraint in the database and no uniqueness validation in ActiveRecord, it
11
+ is possible for your object to validate but then fail to save.
12
+
13
+ See Usage for more info.
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'rescue_unique_constraint'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install rescue_unique_constraint
28
+
29
+ ## Usage
30
+
31
+ Assuming you've added unique index:
32
+
33
+ ```ruby
34
+ class AddIndexToThing < ActiveRecord::Migration
35
+ disable_ddl_transaction!
36
+
37
+ def change
38
+ add_index :things, :somefield, unique: true, algorithm: :concurrently, name: "my_unique_index"
39
+ end
40
+ end
41
+ ```
42
+
43
+ Before:
44
+
45
+ ```ruby
46
+ class Thing < ActiveRecord::Base
47
+ end
48
+
49
+ thing = Thing.create(somefield: "foo")
50
+ dupe = Thing.create(somefield: "foo")
51
+ # => raises ActiveRecord::RecordNotUnique
52
+ ```
53
+
54
+ Note that if you have `validates :uniqueness` in your model, it will prevent
55
+ the RecordNotUnique from being raised in _some_ cases, but not all, as race
56
+ conditions between multiple processes will still cause duplicate entries to
57
+ enter your database.
58
+
59
+ After:
60
+
61
+ ```ruby
62
+ class Thing < ActiveRecord::Base
63
+ include RescueUniqueConstraint
64
+ rescue_unique_constraint index: "my_unique_index", field: "somefield"
65
+ end
66
+
67
+ thing = Thing.create(somefield: "foo")
68
+ dupe = Thing.create(somefield: "foo")
69
+ # => false
70
+ thing.errors[:somefield] == "somefield has already been taken"
71
+ # => true
72
+ # => raises ActiveRecord::RecordNotUnique
73
+ ```
74
+
75
+ ## Testing
76
+
77
+ You'll need a database that supports unique constraints.
78
+ This gem has been tested with PostgreSQL, MySQL and SQLite.
79
+
80
+ ## Contributing
81
+
82
+ 1. [Fork it](https://github.com/reverbdotcom/rescue-unique-constraint/fork)
83
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
84
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
85
+ 4. Push to the branch (`git push origin my-new-feature`)
86
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ begin
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task default: :spec
8
+ rescue LoadError
9
+ end
@@ -0,0 +1,47 @@
1
+ require 'rescue_unique_constraint/version'
2
+ require 'rescue_unique_constraint/index'
3
+ require 'rescue_unique_constraint/rescue_handler'
4
+ require 'rescue_unique_constraint/adapter/mysql_adapter'
5
+ require 'rescue_unique_constraint/adapter/postgresql_adapter'
6
+ require 'rescue_unique_constraint/adapter/sqlite_adapter'
7
+ require 'active_record'
8
+
9
+ # Module which will rescue ActiveRecord::RecordNotUnique exceptions
10
+ # and add errors for indexes that are registered with
11
+ # rescue_unique_constraint(index:, field:)
12
+ module RescueUniqueConstraint
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ # methods mixed into ActiveRecord class
18
+ module ClassMethods
19
+ def index_rescue_handler
20
+ @_index_rescue_handler ||= RescueUniqueConstraint::RescueHandler.new(self)
21
+ end
22
+
23
+ def rescue_unique_constraint(index:, field:, scope: nil)
24
+ unless method_defined?(:create_or_update_with_rescue)
25
+ define_method(:create_or_update_with_rescue) do |*|
26
+ begin
27
+ create_or_update_without_rescue
28
+ rescue ActiveRecord::RecordNotUnique => e
29
+ self.class.index_rescue_handler.matching_indexes(e).each do |matching_index|
30
+ if matching_index.scope
31
+ errors.add(matching_index.field, :taken, scope: matching_index.scope)
32
+ else
33
+ errors.add(matching_index.field, :taken)
34
+ end
35
+ end
36
+ return false
37
+ end
38
+ true
39
+ end
40
+
41
+ alias_method :create_or_update_without_rescue, :create_or_update
42
+ alias_method :create_or_update, :create_or_update_with_rescue
43
+ end
44
+ index_rescue_handler.add_index(index, field)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,9 @@
1
+ module RescueUniqueConstraint
2
+ module Adapter
3
+ class MysqlAdapter
4
+ def index_error?(index, error_message)
5
+ error_message[/#{index.name}/]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ module RescueUniqueConstraint
2
+ module Adapter
3
+ class PostgresqlAdapter
4
+ def index_error?(index, error_message)
5
+ error_message[/#{index.name}/]
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ module RescueUniqueConstraint
2
+ module Adapter
3
+ class SqliteAdapter
4
+ def initialize(table_name)
5
+ @table_name = table_name
6
+ end
7
+
8
+ # Sample error message returned by ActiveRecord for Sqlite Unique exception:
9
+ # 'SQLite3::ConstraintException: UNIQUE constraint failed: things.code, things.score: INSERT INTO "things" ("name", "test", "code", "score") VALUES (?, ?, ?, ?)'
10
+ #
11
+ # Step1: extract column names from above message on which unique constraint failed.
12
+ # Step2: Check if this index's field is among those columns.
13
+ def index_error?(index, error_message)
14
+ column_names = error_message.scan(%r{(?<=#{@table_name}\.)\w+})
15
+ column_names.include?(index.field)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,10 @@
1
+ module RescueUniqueConstraint
2
+ class Index
3
+ attr_reader :name, :field, :scope
4
+ def initialize(name, field, scope = nil)
5
+ @name = name
6
+ @field = field
7
+ @scope = scope
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+ module RescueUniqueConstraint
2
+ # Handles storing and matching [index, field] pairs to exceptions
3
+ class RescueHandler
4
+ def initialize(model)
5
+ @model = model
6
+ @indexes_to_rescue_on = []
7
+ end
8
+
9
+ def add_index(index, field)
10
+ indexes_to_rescue_on << Index.new(index, field)
11
+ end
12
+
13
+ def matching_indexes(e)
14
+ indexes = indexes_to_rescue_on.select do |index|
15
+ database_adapter.index_error?(index, e.message)
16
+ end
17
+ raise e unless indexes.any?
18
+ indexes
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :indexes_to_rescue_on, :model
24
+
25
+ def database_adapter
26
+ @_database_adapter ||= (
27
+ case database_name
28
+ when :mysql2
29
+ Adapter::MysqlAdapter.new
30
+ when :postgresql
31
+ Adapter::PostgresqlAdapter.new
32
+ when :sqlite
33
+ Adapter::SqliteAdapter.new(@model.table_name)
34
+ else
35
+ raise "Database (#{database_name}) not supported"
36
+ end
37
+ )
38
+ end
39
+
40
+ def database_name
41
+ model.connection.adapter_name.downcase.to_sym
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RescueUniqueConstraint
4
+ VERSION = "1.6.0"
5
+ end
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rescue_unique_constraint/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rescue_unique_constraint-cadetstar"
8
+ spec.version = RescueUniqueConstraint::VERSION
9
+ spec.authors = ["Tam Dang", "Yan Pritzker", "Michael Madison"]
10
+ spec.email = ["tam.dang@reverb.com","yan@reverb.com", "cadetstar@hotmail.com"]
11
+ spec.summary = %q{Turns ActiveRecord::RecordNotUnique errors into ActiveRecord errors}
12
+ spec.description = %q{Fork of https://github.com/reverbdotcom/rescue_unique_contraint. Rescues unique constraint violations and turns them into ActiveRecord errors. The fork adds functionality for including scope as an additional option}
13
+ spec.homepage = "https://github.com/cadetstar/rescue_unique_contraint"
14
+ spec.license = "Apache 2.0"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activerecord", ">= 3.2", "< 7"
22
+
23
+ spec.add_development_dependency "bundler", "~> 2.0"
24
+ spec.add_development_dependency "rake", "~> 10.5"
25
+ spec.add_development_dependency "rspec", "~> 3.0"
26
+ spec.add_development_dependency "sqlite3", "~> 1.3"
27
+ spec.add_development_dependency 'pry'
28
+ spec.add_development_dependency 'pry-byebug'
29
+ spec.add_development_dependency 'gem-release'
30
+ end
@@ -0,0 +1,58 @@
1
+ require 'active_record'
2
+ require 'rescue_unique_constraint'
3
+
4
+ describe RescueUniqueConstraint do
5
+ before :all do
6
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
7
+ ActiveRecord::Schema.verbose = false
8
+ ActiveRecord::Schema.define(:version => 1) do
9
+ create_table :things do |t|
10
+ t.string :name
11
+ t.string :test
12
+ t.integer :code
13
+ t.integer :score
14
+ end
15
+
16
+ add_index :things, :name, unique: true, name: "idx_things_on_name_unique"
17
+ add_index :things, :test, unique: true, name: "idx_things_on_test_unique"
18
+ add_index :things, [:code, :score], unique: true, name: "idx_things_on_code_and_score_unique"
19
+ end
20
+ end
21
+
22
+ class Thing < ActiveRecord::Base
23
+ include RescueUniqueConstraint
24
+ rescue_unique_constraint index: "idx_things_on_name_unique", field: "name"
25
+ rescue_unique_constraint index: "idx_things_on_test_unique", field: "test"
26
+ rescue_unique_constraint index: "idx_things_on_code_and_score_unique", field: "score"
27
+ end
28
+
29
+ before :each do
30
+ Thing.destroy_all
31
+ end
32
+
33
+ it "rescues unique constraint violations as activerecord errors" do
34
+ thing = Thing.create(name: "foo", test: 'bar', code: 123, score: 1000)
35
+ dupe = Thing.new(name: "foo", test: 'baz', code: 456, score: 2000)
36
+ expect(dupe.save).to eql false
37
+ expect(dupe.errors.messages.keys).to contain_exactly(:name)
38
+ expect(dupe.errors[:name].first).to match /has already been taken/
39
+ end
40
+
41
+ it "adds error message to atrribute which caused unique-voilation" do
42
+ thing = Thing.create(name: "foo", test: 'bar', code: 123, score: 1000)
43
+ dupe = Thing.new(name: "lorem", test: 'bar', code: 456, score: 2000)
44
+ expect(dupe.save).to eql false
45
+ expect(dupe.errors.messages.keys).to contain_exactly(:test)
46
+ expect(dupe.errors[:test].first).to match /has already been taken/
47
+ end
48
+
49
+ context "When unique contraint is voilated by a composite index" do
50
+ it "adds error message to user defined atrribute" do
51
+ thing = Thing.create(name: "foo", test: 'bar', code: 123, score: 1000)
52
+ dupe = Thing.new(name: "lorem", test: 'ipsum', code: 123, score: 1000)
53
+ expect(dupe.save).to eql false
54
+ expect(dupe.errors.messages.keys).to contain_exactly(:score)
55
+ expect(dupe.errors[:score].first).to match /has already been taken/
56
+ end
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,185 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rescue_unique_constraint-cadetstar
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Tam Dang
8
+ - Yan Pritzker
9
+ - Michael Madison
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2019-12-09 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activerecord
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '3.2'
22
+ - - "<"
23
+ - !ruby/object:Gem::Version
24
+ version: '7'
25
+ type: :runtime
26
+ prerelease: false
27
+ version_requirements: !ruby/object:Gem::Requirement
28
+ requirements:
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: '3.2'
32
+ - - "<"
33
+ - !ruby/object:Gem::Version
34
+ version: '7'
35
+ - !ruby/object:Gem::Dependency
36
+ name: bundler
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '2.0'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2.0'
49
+ - !ruby/object:Gem::Dependency
50
+ name: rake
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '10.5'
56
+ type: :development
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: '10.5'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rspec
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '3.0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.0'
77
+ - !ruby/object:Gem::Dependency
78
+ name: sqlite3
79
+ requirement: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '1.3'
84
+ type: :development
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '1.3'
91
+ - !ruby/object:Gem::Dependency
92
+ name: pry
93
+ requirement: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ type: :development
99
+ prerelease: false
100
+ version_requirements: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ - !ruby/object:Gem::Dependency
106
+ name: pry-byebug
107
+ requirement: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ type: :development
113
+ prerelease: false
114
+ version_requirements: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ - !ruby/object:Gem::Dependency
120
+ name: gem-release
121
+ requirement: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ type: :development
127
+ prerelease: false
128
+ version_requirements: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ description: Fork of https://github.com/reverbdotcom/rescue_unique_contraint. Rescues
134
+ unique constraint violations and turns them into ActiveRecord errors. The fork adds
135
+ functionality for including scope as an additional option
136
+ email:
137
+ - tam.dang@reverb.com
138
+ - yan@reverb.com
139
+ - cadetstar@hotmail.com
140
+ executables: []
141
+ extensions: []
142
+ extra_rdoc_files: []
143
+ files:
144
+ - ".gitignore"
145
+ - ".rubocop.yml"
146
+ - ".ruby-version"
147
+ - Gemfile
148
+ - LICENSE.txt
149
+ - README.md
150
+ - Rakefile
151
+ - lib/rescue_unique_constraint.rb
152
+ - lib/rescue_unique_constraint/adapter/mysql_adapter.rb
153
+ - lib/rescue_unique_constraint/adapter/postgresql_adapter.rb
154
+ - lib/rescue_unique_constraint/adapter/sqlite_adapter.rb
155
+ - lib/rescue_unique_constraint/index.rb
156
+ - lib/rescue_unique_constraint/rescue_handler.rb
157
+ - lib/rescue_unique_constraint/version.rb
158
+ - rescue_unique_constraint-cadetstar.gemspec
159
+ - spec/rescue_unique_constraint_spec.rb
160
+ homepage: https://github.com/cadetstar/rescue_unique_contraint
161
+ licenses:
162
+ - Apache 2.0
163
+ metadata: {}
164
+ post_install_message:
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
173
+ required_rubygems_version: !ruby/object:Gem::Requirement
174
+ requirements:
175
+ - - ">="
176
+ - !ruby/object:Gem::Version
177
+ version: '0'
178
+ requirements: []
179
+ rubyforge_project:
180
+ rubygems_version: 2.7.6
181
+ signing_key:
182
+ specification_version: 4
183
+ summary: Turns ActiveRecord::RecordNotUnique errors into ActiveRecord errors
184
+ test_files:
185
+ - spec/rescue_unique_constraint_spec.rb