rescue_unique_constraint-cadetstar 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
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