database_validations 0.3.6 → 0.3.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/lib/database_validations.rb +2 -0
- data/lib/database_validations/adapters.rb +3 -3
- data/lib/database_validations/adapters/base_adapter.rb +32 -6
- data/lib/database_validations/adapters/mysql_adapter.rb +4 -1
- data/lib/database_validations/adapters/postgresql_adapter.rb +4 -1
- data/lib/database_validations/adapters/sqlite_adapter.rb +3 -0
- data/lib/database_validations/errors.rb +11 -0
- data/lib/database_validations/helpers.rb +24 -26
- data/lib/database_validations/uniqueness_options.rb +48 -0
- data/lib/database_validations/uniqueness_options_storage.rb +26 -0
- data/lib/database_validations/uniqueness_validator.rb +8 -13
- data/lib/database_validations/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aefb40cecf33b68606f21a491cd41ffa3e1bc4ffcf891b837b9b8693e04f2239
|
4
|
+
data.tar.gz: ec3c2ab6955ec4f5da93804bcb6f2382d577331553b9d710af75a62cf9e8ccea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1c341fa892d214a3fce105e683c90b5633c7c8ccc5917c675762e710cde2443e332bf66768831b772436c6eb6bfd823136411f409e0c16ef58cb85347f8b87c8
|
7
|
+
data.tar.gz: c6a838adc69c1558acede0e38df24d30bac20d7c348951c8059966daf59e649ae7887879810e68f88df88d69b277451a7fe476bca8a237842c970103184c073b
|
data/lib/database_validations.rb
CHANGED
@@ -3,6 +3,8 @@ require 'active_record'
|
|
3
3
|
require 'database_validations/version'
|
4
4
|
require 'database_validations/railtie' if defined?(Rails)
|
5
5
|
require 'database_validations/uniqueness_validator'
|
6
|
+
require 'database_validations/uniqueness_options'
|
7
|
+
require 'database_validations/uniqueness_options_storage'
|
6
8
|
require 'database_validations/errors'
|
7
9
|
require 'database_validations/helpers'
|
8
10
|
require 'database_validations/adapters'
|
@@ -9,9 +9,9 @@ module DatabaseValidations
|
|
9
9
|
|
10
10
|
def factory(model)
|
11
11
|
case (database = model.connection.adapter_name.downcase.to_sym)
|
12
|
-
when
|
13
|
-
when
|
14
|
-
when
|
12
|
+
when SqliteAdapter::ADAPTER then SqliteAdapter.new(model)
|
13
|
+
when PostgresqlAdapter::ADAPTER then PostgresqlAdapter.new(model)
|
14
|
+
when MysqlAdapter::ADAPTER then MysqlAdapter.new(model)
|
15
15
|
else
|
16
16
|
raise Errors::UnknownDatabase.new(database)
|
17
17
|
end
|
@@ -1,18 +1,44 @@
|
|
1
1
|
module DatabaseValidations
|
2
2
|
module Adapters
|
3
3
|
class BaseAdapter
|
4
|
-
|
4
|
+
SUPPORTED_OPTIONS = [].freeze
|
5
|
+
ADAPTER = :base
|
5
6
|
|
6
7
|
def initialize(model)
|
7
8
|
@model = model
|
8
9
|
end
|
9
10
|
|
10
|
-
def
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
def find_index_by_name(index_name)
|
12
|
+
indexes.find { |index| index.name == index_name }
|
13
|
+
end
|
14
|
+
|
15
|
+
def find_index_by_columns(index_columns)
|
16
|
+
indexes.find { |index| index.columns.map(&:to_s).sort == index_columns }
|
17
|
+
end
|
18
|
+
|
19
|
+
def indexes
|
20
|
+
model.connection.indexes(model.table_name).select(&:unique)
|
21
|
+
end
|
22
|
+
|
23
|
+
# @return [Symbol]
|
24
|
+
def adapter_name
|
25
|
+
self.class::ADAPTER
|
15
26
|
end
|
27
|
+
|
28
|
+
# @param [Symbol] option_name
|
29
|
+
# @return [Boolean]
|
30
|
+
def support_option?(option_name)
|
31
|
+
supported_options.include?(option_name.to_sym)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [Array<Symbol>]
|
35
|
+
def supported_options
|
36
|
+
self.class::SUPPORTED_OPTIONS
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :model
|
16
42
|
end
|
17
43
|
end
|
18
44
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
module DatabaseValidations
|
2
2
|
module Adapters
|
3
3
|
class MysqlAdapter < BaseAdapter
|
4
|
+
SUPPORTED_OPTIONS = %i[scope message].freeze
|
5
|
+
ADAPTER = :mysql2
|
6
|
+
|
4
7
|
def error_columns(error_message)
|
5
8
|
index_name = error_message[/key '([^']+)'/, 1]
|
6
|
-
|
9
|
+
find_index_by_name(index_name).columns
|
7
10
|
end
|
8
11
|
end
|
9
12
|
end
|
@@ -1,9 +1,12 @@
|
|
1
1
|
module DatabaseValidations
|
2
2
|
module Adapters
|
3
3
|
class PostgresqlAdapter < BaseAdapter
|
4
|
+
SUPPORTED_OPTIONS = %i[scope message].freeze
|
5
|
+
ADAPTER = :postgresql
|
6
|
+
|
4
7
|
def error_columns(error_message)
|
5
8
|
index_name = error_message[/unique constraint "([^"]+)"/, 1]
|
6
|
-
|
9
|
+
find_index_by_name(index_name).columns
|
7
10
|
end
|
8
11
|
end
|
9
12
|
end
|
@@ -21,5 +21,16 @@ module DatabaseValidations
|
|
21
21
|
super "Unknown database: #{self.database}"
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
class OptionIsNotSupported < Base
|
26
|
+
attr_reader :option, :database, :supported_options
|
27
|
+
|
28
|
+
def initialize(option, database, supported_options)
|
29
|
+
@option = option
|
30
|
+
@database = database
|
31
|
+
@supported_options = supported_options
|
32
|
+
super "Option #{self.option} is not supported for #{self.database}. Supported options are: #{self.supported_options}"
|
33
|
+
end
|
34
|
+
end
|
24
35
|
end
|
25
36
|
end
|
@@ -2,39 +2,37 @@ module DatabaseValidations
|
|
2
2
|
module Helpers
|
3
3
|
module_function
|
4
4
|
|
5
|
-
def
|
6
|
-
|
7
|
-
.indexes(model.table_name)
|
8
|
-
.select(&:unique)
|
9
|
-
.find { |index| index.columns.map(&:to_s).sort == columns }
|
5
|
+
def handle_unique_error!(instance, error)
|
6
|
+
key = generate_key(Adapters.factory(instance.class).error_columns(error.message))
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
columns = DatabaseValidations::Adapters
|
16
|
-
.factory(instance.class)
|
17
|
-
.error_columns(error.message)
|
18
|
-
.map!(&:to_s)
|
19
|
-
.sort!
|
20
|
-
|
21
|
-
options = uniqueness_validators_options(instance.class)[columns]
|
22
|
-
|
23
|
-
error_options = options.except(:case_sensitive, :scope, :conditions, :attributes)
|
24
|
-
error_options[:value] = instance.public_send(options[:attributes])
|
8
|
+
each_options_storage(instance.class) do |storage|
|
9
|
+
# storage has this then return
|
10
|
+
return storage[key].handle_unique_error(instance) if storage[key]
|
11
|
+
end
|
25
12
|
|
26
|
-
|
13
|
+
raise error
|
27
14
|
end
|
28
15
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
validators_options.reverse_merge!(klass.superclass.instance_variable_get(:'@validates_db_uniqueness_opts') || {})
|
16
|
+
def each_options_storage(klass)
|
17
|
+
while klass.respond_to?(:validates_db_uniqueness_of)
|
18
|
+
storage = klass.instance_variable_get(:'@validates_db_uniqueness_opts')
|
19
|
+
yield(storage) if storage
|
34
20
|
klass = klass.superclass
|
35
21
|
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def each_validator(klass)
|
25
|
+
each_options_storage(klass) do |storage|
|
26
|
+
storage.each_validator { |validator| yield(validator) }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def unify_columns(*columns)
|
31
|
+
columns.flatten.map(&:to_s).sort
|
32
|
+
end
|
36
33
|
|
37
|
-
|
34
|
+
def generate_key(*columns)
|
35
|
+
unify_columns(columns).join('__')
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
class UniquenessOptions
|
3
|
+
def initialize(field, options, adapter)
|
4
|
+
@field = field
|
5
|
+
@options = options
|
6
|
+
@adapter = adapter
|
7
|
+
|
8
|
+
raise_if_unsupported_options!
|
9
|
+
raise_if_index_missed! unless ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle_unique_error(instance)
|
13
|
+
error_options = options.except(:case_sensitive, :scope, :conditions, :attributes)
|
14
|
+
error_options[:value] = instance.public_send(options[:attributes])
|
15
|
+
|
16
|
+
instance.errors.add(options[:attributes], :taken, error_options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def validates_uniqueness_options
|
20
|
+
options.merge(allow_nil: true, case_sensitive: true, allow_blank: false)
|
21
|
+
end
|
22
|
+
|
23
|
+
def key
|
24
|
+
@key ||= Helpers.generate_key(columns)
|
25
|
+
end
|
26
|
+
|
27
|
+
def columns
|
28
|
+
@columns ||= Helpers.unify_columns(field, Array.wrap(options[:scope]))
|
29
|
+
end
|
30
|
+
|
31
|
+
def raise_if_unsupported_options!
|
32
|
+
options.except(:attributes).each_key do |option|
|
33
|
+
unless adapter.support_option?(option)
|
34
|
+
raise Errors::OptionIsNotSupported.new(option, adapter.adapter_name, adapter.supported_options)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def raise_if_index_missed!
|
40
|
+
raise Errors::IndexNotFound.new(columns) unless adapter.find_index_by_columns(columns)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :adapter, :field, :options
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
class UniquenessOptionsStorage
|
3
|
+
|
4
|
+
def initialize(klass)
|
5
|
+
@adapter = Adapters.factory(klass)
|
6
|
+
@storage = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def push(field, options)
|
10
|
+
uniqueness_options = UniquenessOptions.new(field, options, adapter)
|
11
|
+
storage[uniqueness_options.key] = uniqueness_options
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](key)
|
15
|
+
storage[key]
|
16
|
+
end
|
17
|
+
|
18
|
+
def each_validator
|
19
|
+
storage.each_value { |validator| yield(validator) }
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :storage, :adapter
|
25
|
+
end
|
26
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module DatabaseValidations
|
2
|
-
module
|
2
|
+
module UniquenessHandlers
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
included do
|
@@ -8,9 +8,8 @@ module DatabaseValidations
|
|
8
8
|
def valid?(context = nil)
|
9
9
|
output = super(context)
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
validates_with(ActiveRecord::Validations::UniquenessValidator, opts.merge(allow_nil: true))
|
11
|
+
Helpers.each_validator(self.class) do |validator|
|
12
|
+
validates_with(ActiveRecord::Validations::UniquenessValidator, validator.validates_uniqueness_options)
|
14
13
|
end
|
15
14
|
|
16
15
|
errors.empty? && output
|
@@ -22,14 +21,14 @@ module DatabaseValidations
|
|
22
21
|
def save(options = {})
|
23
22
|
ActiveRecord::Base.connection.transaction(requires_new: true) { super }
|
24
23
|
rescue ActiveRecord::RecordNotUnique => e
|
25
|
-
|
24
|
+
Helpers.handle_unique_error!(self, e)
|
26
25
|
false
|
27
26
|
end
|
28
27
|
|
29
28
|
def save!(options = {})
|
30
29
|
ActiveRecord::Base.connection.transaction(requires_new: true) { super }
|
31
30
|
rescue ActiveRecord::RecordNotUnique => e
|
32
|
-
|
31
|
+
Helpers.handle_unique_error!(self, e)
|
33
32
|
raise ActiveRecord::RecordInvalid, self
|
34
33
|
end
|
35
34
|
|
@@ -42,19 +41,15 @@ module DatabaseValidations
|
|
42
41
|
|
43
42
|
module ClassMethods
|
44
43
|
def validates_db_uniqueness_of(*attributes)
|
45
|
-
@validates_db_uniqueness_opts ||=
|
44
|
+
@validates_db_uniqueness_opts ||= DatabaseValidations::UniquenessOptionsStorage.new(self)
|
46
45
|
|
47
46
|
options = attributes.extract_options!
|
48
47
|
|
49
48
|
attributes.each do |attribute|
|
50
|
-
|
51
|
-
|
52
|
-
DatabaseValidations::Helpers.raise_if_index_missed!(self, columns) unless ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
|
53
|
-
|
54
|
-
@validates_db_uniqueness_opts[columns] = options.merge(attributes: attribute)
|
49
|
+
@validates_db_uniqueness_opts.push(attribute, options.merge(attributes: attribute))
|
55
50
|
end
|
56
51
|
|
57
|
-
include(
|
52
|
+
include(DatabaseValidations::UniquenessHandlers)
|
58
53
|
end
|
59
54
|
end
|
60
55
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: database_validations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.3.
|
4
|
+
version: 0.3.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evgeniy Demin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-09-
|
11
|
+
date: 2018-09-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -152,6 +152,8 @@ files:
|
|
152
152
|
- lib/database_validations/errors.rb
|
153
153
|
- lib/database_validations/helpers.rb
|
154
154
|
- lib/database_validations/railtie.rb
|
155
|
+
- lib/database_validations/uniqueness_options.rb
|
156
|
+
- lib/database_validations/uniqueness_options_storage.rb
|
155
157
|
- lib/database_validations/uniqueness_validator.rb
|
156
158
|
- lib/database_validations/version.rb
|
157
159
|
- lib/tasks/database_validations.rake
|