database_validations 0.8.9 → 0.9.3
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 +12 -9
- data/lib/database_validations/lib/adapters/base_adapter.rb +0 -16
- data/lib/database_validations/lib/adapters/mysql_adapter.rb +1 -2
- data/lib/database_validations/lib/adapters/postgresql_adapter.rb +0 -1
- data/lib/database_validations/lib/adapters/sqlite_adapter.rb +0 -1
- data/lib/database_validations/lib/attribute_validator.rb +3 -0
- data/lib/database_validations/lib/checkers/db_presence_validator.rb +36 -0
- data/lib/database_validations/lib/checkers/db_uniqueness_validator.rb +47 -0
- data/lib/database_validations/lib/errors.rb +0 -11
- data/lib/database_validations/lib/injector.rb +13 -0
- data/lib/database_validations/lib/key_generator.rb +32 -0
- data/lib/database_validations/lib/presence_key_extractor.rb +18 -0
- data/lib/database_validations/lib/rescuer.rb +28 -17
- data/lib/database_validations/lib/storage.rb +28 -0
- data/lib/database_validations/lib/uniqueness_key_extractor.rb +38 -0
- data/lib/database_validations/lib/validations.rb +31 -0
- data/lib/database_validations/lib/validators/db_presence_validator.rb +67 -0
- data/lib/database_validations/lib/validators/db_uniqueness_validator.rb +65 -0
- data/lib/database_validations/rspec/uniqueness_validator_matcher.rb +15 -8
- data/lib/database_validations/version.rb +1 -1
- metadata +45 -35
- data/lib/database_validations/lib/db_belongs_to/belongs_to_handlers.rb +0 -20
- data/lib/database_validations/lib/db_belongs_to/belongs_to_options.rb +0 -39
- data/lib/database_validations/lib/db_belongs_to/db_presence_validator.rb +0 -30
- data/lib/database_validations/lib/helpers.rb +0 -79
- data/lib/database_validations/lib/options_storage.rb +0 -31
- data/lib/database_validations/lib/validates_db_uniqueness_of/db_uniqueness_validator.rb +0 -14
- data/lib/database_validations/lib/validates_db_uniqueness_of/uniqueness_handlers.rb +0 -20
- data/lib/database_validations/lib/validates_db_uniqueness_of/uniqueness_options.rb +0 -98
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 37040902b8f9dd94cbfcc1785129eff2ac2f75eb085f567299852a03f4a83cfa
|
4
|
+
data.tar.gz: c6a2a3331225b8344c23244692296a982ed25405be15650c32bc0815e227c20a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27f9f74a6f081f343bbe94da77956cd0c06419dcf321d2576a6238f0b2bddb5dd967861deaffe88ab47929e4b3837825fc9c17bf95b4fba83f5fbfd4b07d04e0
|
7
|
+
data.tar.gz: 86f379332348da451c1f2d02e3e4096e528f9e00c25f10fdb8881512d97c10ffdf9ab5cffdc3f4e9ce6ea475e0d3d0a0d75b728fed700f7606ccf07cacfe9bdf
|
data/lib/database_validations.rb
CHANGED
@@ -4,18 +4,21 @@ require 'database_validations/version'
|
|
4
4
|
|
5
5
|
require 'database_validations/rails/railtie' if defined?(Rails)
|
6
6
|
|
7
|
-
require 'database_validations/lib/
|
8
|
-
require 'database_validations/lib/
|
9
|
-
require 'database_validations/lib/validates_db_uniqueness_of/uniqueness_options'
|
7
|
+
require 'database_validations/lib/checkers/db_uniqueness_validator'
|
8
|
+
require 'database_validations/lib/checkers/db_presence_validator'
|
10
9
|
|
11
|
-
require 'database_validations/lib/
|
12
|
-
require 'database_validations/lib/
|
13
|
-
require 'database_validations/lib/db_belongs_to/belongs_to_options'
|
10
|
+
require 'database_validations/lib/validators/db_uniqueness_validator'
|
11
|
+
require 'database_validations/lib/validators/db_presence_validator'
|
14
12
|
|
15
|
-
require 'database_validations/lib/
|
16
|
-
require 'database_validations/lib/
|
13
|
+
require 'database_validations/lib/storage'
|
14
|
+
require 'database_validations/lib/attribute_validator'
|
15
|
+
require 'database_validations/lib/key_generator'
|
16
|
+
require 'database_validations/lib/uniqueness_key_extractor'
|
17
|
+
require 'database_validations/lib/presence_key_extractor'
|
18
|
+
require 'database_validations/lib/validations'
|
17
19
|
require 'database_validations/lib/errors'
|
18
|
-
require 'database_validations/lib/
|
20
|
+
require 'database_validations/lib/rescuer'
|
21
|
+
require 'database_validations/lib/injector'
|
19
22
|
require 'database_validations/lib/adapters'
|
20
23
|
|
21
24
|
module DatabaseValidations
|
@@ -31,22 +31,6 @@ module DatabaseValidations
|
|
31
31
|
foreign_keys.find { |foreign_key| foreign_key.column.to_s == column.to_s }
|
32
32
|
end
|
33
33
|
|
34
|
-
# @return [Symbol]
|
35
|
-
def adapter_name
|
36
|
-
self.class::ADAPTER
|
37
|
-
end
|
38
|
-
|
39
|
-
# @param [Symbol] option_name
|
40
|
-
# @return [Boolean]
|
41
|
-
def support_option?(option_name)
|
42
|
-
supported_options.include?(option_name.to_sym)
|
43
|
-
end
|
44
|
-
|
45
|
-
# @return [Array<Symbol>]
|
46
|
-
def supported_options
|
47
|
-
self.class::SUPPORTED_OPTIONS
|
48
|
-
end
|
49
|
-
|
50
34
|
# @return [String]
|
51
35
|
def table_name
|
52
36
|
model.table_name
|
@@ -1,12 +1,11 @@
|
|
1
1
|
module DatabaseValidations
|
2
2
|
module Adapters
|
3
3
|
class MysqlAdapter < BaseAdapter
|
4
|
-
SUPPORTED_OPTIONS = %i[scope message if unless index_name].freeze
|
5
4
|
ADAPTER = :mysql2
|
6
5
|
|
7
6
|
class << self
|
8
7
|
def unique_index_name(error_message)
|
9
|
-
error_message[/key '([^']+)'/, 1]
|
8
|
+
error_message[/key '([^']+)'/, 1]&.split('.')&.last
|
10
9
|
end
|
11
10
|
|
12
11
|
def unique_error_columns(_error_message); end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Checkers
|
3
|
+
class DbPresenceValidator
|
4
|
+
attr_reader :validator
|
5
|
+
|
6
|
+
# @param [DatabaseValidations::DbPresenceValidator]
|
7
|
+
def self.validate!(validator)
|
8
|
+
new(validator).validate!
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [DatabaseValidations::DbPresenceValidator]
|
12
|
+
def initialize(validator)
|
13
|
+
@validator = validator
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate!
|
17
|
+
validate_foreign_keys! unless ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def validate_foreign_keys! # rubocop:disable Metrics/AbcSize
|
23
|
+
adapter = Adapters::BaseAdapter.new(validator.klass)
|
24
|
+
|
25
|
+
validator.attributes.each do |attribute|
|
26
|
+
reflection = validator.klass._reflect_on_association(attribute)
|
27
|
+
|
28
|
+
next unless reflection
|
29
|
+
next if adapter.find_foreign_key_by_column(reflection.foreign_key)
|
30
|
+
|
31
|
+
raise Errors::ForeignKeyNotFound.new(reflection.foreign_key, adapter.foreign_keys)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Checkers
|
3
|
+
class DbUniquenessValidator
|
4
|
+
attr_reader :validator
|
5
|
+
|
6
|
+
# @param [DatabaseValidations::DbUniquenessValidator]
|
7
|
+
def self.validate!(validator)
|
8
|
+
new(validator).validate!
|
9
|
+
end
|
10
|
+
|
11
|
+
# @param [DatabaseValidations::DbUniquenessValidator]
|
12
|
+
def initialize(validator)
|
13
|
+
@validator = validator
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate!
|
17
|
+
validate_index_usage!
|
18
|
+
|
19
|
+
validate_indexes! unless ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def valid_index?(columns, index)
|
25
|
+
index_columns_size = index.columns.is_a?(Array) ? index.columns.size : (index.columns.count(',') + 1)
|
26
|
+
|
27
|
+
(columns.size == index_columns_size) && (validator.where.nil? == index.where.nil?)
|
28
|
+
end
|
29
|
+
|
30
|
+
def validate_index_usage!
|
31
|
+
return unless validator.index_name.present? && validator.attributes.size > 1
|
32
|
+
|
33
|
+
raise ArgumentError, "When index_name is provided validator can have only one attribute. See #{validator.inspect}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_indexes! # rubocop:disable Metrics/AbcSize
|
37
|
+
adapter = Adapters::BaseAdapter.new(validator.klass)
|
38
|
+
|
39
|
+
validator.attributes.map do |attribute|
|
40
|
+
columns = KeyGenerator.unify_columns(attribute, validator.options[:scope])
|
41
|
+
index = validator.index_name ? adapter.find_index_by_name(validator.index_name.to_s) : adapter.find_index(columns, validator.where) # rubocop:disable Metrics/LineLength
|
42
|
+
raise Errors::IndexNotFound.new(columns, validator.where, validator.index_name, adapter.indexes, adapter.table_name) unless index && valid_index?(columns, index) # rubocop:disable Metrics/LineLength
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -41,17 +41,6 @@ module DatabaseValidations
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
class OptionIsNotSupported < Base
|
45
|
-
attr_reader :option, :database, :supported_options
|
46
|
-
|
47
|
-
def initialize(option, database, supported_options)
|
48
|
-
@option = option
|
49
|
-
@database = database
|
50
|
-
@supported_options = supported_options
|
51
|
-
super "Option #{self.option} is not supported for #{self.database}. Supported options are: #{self.supported_options}"
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
44
|
class ForeignKeyNotFound < Base
|
56
45
|
attr_reader :column, :foreign_keys
|
57
46
|
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Injector
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# @param [ActiveRecord::Base] model
|
6
|
+
def inject(model)
|
7
|
+
return if model.method_defined?(:valid_without_database_validations?)
|
8
|
+
|
9
|
+
model.__send__(:alias_method, :valid_without_database_validations?, :valid?)
|
10
|
+
model.include(DatabaseValidations::Validations)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module KeyGenerator
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# @param [String] index_name
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
def for_unique_index(index_name)
|
9
|
+
generate_key(:unique_index, index_name)
|
10
|
+
end
|
11
|
+
|
12
|
+
# @return [String]
|
13
|
+
def for_db_uniqueness(*columns)
|
14
|
+
generate_key(:db_uniqueness, columns)
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [String]
|
18
|
+
def for_db_presence(column)
|
19
|
+
generate_key(:db_presence, column)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [String]
|
23
|
+
def generate_key(type, *args)
|
24
|
+
[type, *unify_columns(args)].join('__')
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String]
|
28
|
+
def unify_columns(*args)
|
29
|
+
args.flatten.compact.map(&:to_s).sort
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module PresenceKeyExtractor
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# @param [DatabaseValidations::DbPresenceValidator]
|
6
|
+
#
|
7
|
+
# @return [Hash]
|
8
|
+
def attribute_by_key(validator)
|
9
|
+
validator.attributes.map do |attribute|
|
10
|
+
reflection = validator.klass._reflect_on_association(attribute)
|
11
|
+
|
12
|
+
key = reflection ? reflection.foreign_key : attribute
|
13
|
+
|
14
|
+
[KeyGenerator.for_db_presence(key), attribute]
|
15
|
+
end.to_h
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -1,31 +1,42 @@
|
|
1
1
|
module DatabaseValidations
|
2
2
|
module Rescuer
|
3
|
-
|
3
|
+
module_function
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
def handled?(instance, error)
|
6
|
+
Storage.prepare(instance.class) unless Storage.prepared?(instance.class)
|
7
|
+
|
8
|
+
case error
|
9
|
+
when ActiveRecord::RecordNotUnique
|
10
|
+
process(instance, error, for_unique_index: :unique_index_name, for_db_uniqueness: :unique_error_columns)
|
11
|
+
when ActiveRecord::InvalidForeignKey
|
12
|
+
process(instance, error, for_db_presence: :foreign_key_error_column)
|
13
|
+
else false
|
14
|
+
end
|
7
15
|
end
|
8
16
|
|
9
|
-
|
17
|
+
def process(instance, error, key_types)
|
18
|
+
adapter = Adapters.factory(instance.class)
|
10
19
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
20
|
+
keys = key_types.map do |key_generator, error_processor|
|
21
|
+
KeyGenerator.public_send(key_generator, adapter.public_send(error_processor, error.message))
|
22
|
+
end
|
23
|
+
|
24
|
+
keys.each do |key|
|
25
|
+
attribute_validator = instance._db_validators[key]
|
26
|
+
|
27
|
+
next unless attribute_validator
|
15
28
|
|
16
|
-
|
17
|
-
|
18
|
-
ActiveRecord::Base.connection.transaction(requires_new: true) { super }
|
19
|
-
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::RecordNotUnique => error
|
20
|
-
raise error unless Helpers.handle_error!(self, error)
|
29
|
+
return process_validator(instance, attribute_validator)
|
30
|
+
end
|
21
31
|
|
22
|
-
|
32
|
+
false
|
23
33
|
end
|
24
34
|
|
25
|
-
|
35
|
+
def process_validator(instance, attribute_validator)
|
36
|
+
return false unless attribute_validator.validator.perform_db_validation?
|
26
37
|
|
27
|
-
|
28
|
-
|
38
|
+
attribute_validator.validator.apply_error(instance, attribute_validator.attribute)
|
39
|
+
true
|
29
40
|
end
|
30
41
|
end
|
31
42
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Storage
|
3
|
+
module_function
|
4
|
+
|
5
|
+
def prepare(model)
|
6
|
+
model.class_attribute :_db_validators, instance_writer: false
|
7
|
+
model._db_validators = {}
|
8
|
+
|
9
|
+
model.validators.each do |validator|
|
10
|
+
case validator
|
11
|
+
when DbUniquenessValidator then process(validator, UniquenessKeyExtractor, model)
|
12
|
+
when DbPresenceValidator then process(validator, PresenceKeyExtractor, model)
|
13
|
+
else next
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def process(validator, extractor, model)
|
19
|
+
extractor.attribute_by_key(validator).each do |key, attribute|
|
20
|
+
model._db_validators[key] = AttributeValidator.new(attribute, validator)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def prepared?(model)
|
25
|
+
model.respond_to?(:_db_validators)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module UniquenessKeyExtractor
|
3
|
+
module_function
|
4
|
+
|
5
|
+
# @param [DatabaseValidations::DbUniquenessValidator]
|
6
|
+
#
|
7
|
+
# @return [Hash]
|
8
|
+
def attribute_by_columns_keys(validator)
|
9
|
+
validator.attributes.map do |attribute|
|
10
|
+
[KeyGenerator.for_db_uniqueness(attribute, Array.wrap(validator.options[:scope])), attribute]
|
11
|
+
end.to_h
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [DatabaseValidations::DbUniquenessValidator]
|
15
|
+
#
|
16
|
+
# @return [Hash]
|
17
|
+
def attribute_by_indexes_keys(validator) # rubocop:disable Metrics/AbcSize
|
18
|
+
adapter = Adapters::BaseAdapter.new(validator.klass)
|
19
|
+
|
20
|
+
if validator.index_name
|
21
|
+
[[KeyGenerator.for_unique_index(validator.index_name), validator.attributes[0]]].to_h
|
22
|
+
else
|
23
|
+
validator.attributes.map do |attribute|
|
24
|
+
columns = KeyGenerator.unify_columns(attribute, validator.options[:scope])
|
25
|
+
index = adapter.find_index(columns, validator.where)
|
26
|
+
[KeyGenerator.for_unique_index(index.name), attribute]
|
27
|
+
end.to_h
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# @param [DatabaseValidations::DbUniquenessValidator]
|
32
|
+
#
|
33
|
+
# @return [Hash]
|
34
|
+
def attribute_by_key(validator)
|
35
|
+
attribute_by_columns_keys(validator).merge(attribute_by_indexes_keys(validator))
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Validations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method :validate, :valid?
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_accessor :_database_validations_fallback
|
10
|
+
|
11
|
+
def valid?(context = nil)
|
12
|
+
self._database_validations_fallback = true
|
13
|
+
super(context)
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_or_update(*args, &block)
|
17
|
+
self._database_validations_fallback = false
|
18
|
+
ActiveRecord::Base.connection.transaction(requires_new: true) { super }
|
19
|
+
rescue ActiveRecord::InvalidForeignKey, ActiveRecord::RecordNotUnique => e
|
20
|
+
raise e unless Rescuer.handled?(self, e)
|
21
|
+
|
22
|
+
raise ActiveRecord::RecordInvalid, self
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def perform_validations(options = {})
|
28
|
+
options[:validate] == false || valid_without_database_validations?(options[:context])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
class DbPresenceValidator < ActiveRecord::Validations::PresenceValidator
|
3
|
+
REFLECTION_MESSAGE = ActiveRecord::VERSION::MAJOR < 5 ? :blank : :required
|
4
|
+
|
5
|
+
attr_reader :klass
|
6
|
+
|
7
|
+
# Used to make 3rd party libraries work correctly
|
8
|
+
#
|
9
|
+
# @return [Symbol]
|
10
|
+
def self.kind
|
11
|
+
:presence
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Hash] options
|
15
|
+
def initialize(options)
|
16
|
+
@klass = options[:class]
|
17
|
+
|
18
|
+
super
|
19
|
+
|
20
|
+
Injector.inject(klass)
|
21
|
+
Checkers::DbPresenceValidator.validate!(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
def perform_db_validation?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
# TODO: add support of optional db_belongs_to
|
29
|
+
def validate(record)
|
30
|
+
if record._database_validations_fallback
|
31
|
+
super
|
32
|
+
else
|
33
|
+
attributes.each do |attribute|
|
34
|
+
reflection = record.class._reflect_on_association(attribute)
|
35
|
+
|
36
|
+
next if reflection && record.public_send(reflection.foreign_key).present?
|
37
|
+
|
38
|
+
validate_each(record, attribute, record.public_send(attribute))
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def apply_error(instance, attribute)
|
44
|
+
# Helps to avoid querying the database when attribute is association
|
45
|
+
instance.send("#{attribute}=", nil)
|
46
|
+
instance.errors.add(attribute, :blank, message: REFLECTION_MESSAGE)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module ClassMethods
|
51
|
+
def validates_db_presence_of(*attr_names)
|
52
|
+
validates_with(DatabaseValidations::DbPresenceValidator, _merge_attributes(attr_names))
|
53
|
+
end
|
54
|
+
|
55
|
+
def db_belongs_to(name, scope = nil, **options)
|
56
|
+
if ActiveRecord::VERSION::MAJOR < 5
|
57
|
+
options[:required] = false
|
58
|
+
else
|
59
|
+
options[:optional] = true
|
60
|
+
end
|
61
|
+
|
62
|
+
belongs_to(name, scope, **options)
|
63
|
+
|
64
|
+
validates_with DatabaseValidations::DbPresenceValidator, _merge_attributes([name, message: DatabaseValidations::DbPresenceValidator::REFLECTION_MESSAGE]) # rubocop:disable Metrics/LineLength
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
class DbUniquenessValidator < ActiveRecord::Validations::UniquenessValidator
|
3
|
+
DEFAULT_MODE = :optimized
|
4
|
+
|
5
|
+
attr_reader :index_name, :where, :klass
|
6
|
+
|
7
|
+
# Used to make 3rd party libraries work correctly
|
8
|
+
#
|
9
|
+
# @return [Symbol]
|
10
|
+
def self.kind
|
11
|
+
:uniqueness
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [Hash] options
|
15
|
+
def initialize(options)
|
16
|
+
options[:allow_nil] = true
|
17
|
+
options[:allow_blank] = false
|
18
|
+
|
19
|
+
if options.key?(:where)
|
20
|
+
condition = options[:where]
|
21
|
+
options[:conditions] = -> { where(condition) }
|
22
|
+
end
|
23
|
+
|
24
|
+
handle_custom_options(options)
|
25
|
+
|
26
|
+
super
|
27
|
+
|
28
|
+
Injector.inject(klass)
|
29
|
+
Checkers::DbUniquenessValidator.validate!(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
def perform_db_validation?
|
33
|
+
@mode != :standard
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate(record)
|
37
|
+
super if perform_query? || record._database_validations_fallback
|
38
|
+
end
|
39
|
+
|
40
|
+
def apply_error(instance, attribute)
|
41
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
42
|
+
error_options[:value] = instance.public_send(attribute)
|
43
|
+
|
44
|
+
instance.errors.add(attribute, :taken, error_options)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def handle_custom_options(options)
|
50
|
+
@index_name = options.delete(:index_name) if options.key?(:index_name)
|
51
|
+
@where = options.delete(:where) if options.key?(:where)
|
52
|
+
@mode = (options.delete(:mode).presence || DEFAULT_MODE).to_sym
|
53
|
+
end
|
54
|
+
|
55
|
+
def perform_query?
|
56
|
+
@mode != :optimized
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module ClassMethods
|
61
|
+
def validates_db_uniqueness_of(*attr_names)
|
62
|
+
validates_with(DatabaseValidations::DbUniquenessValidator, _merge_attributes(attr_names))
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -41,31 +41,37 @@ RSpec::Matchers.define :validate_db_uniqueness_of do |field| # rubocop:disable M
|
|
41
41
|
@case_sensitive = false
|
42
42
|
end
|
43
43
|
|
44
|
+
chain(:case_sensitive) do
|
45
|
+
@case_sensitive = true
|
46
|
+
end
|
47
|
+
|
44
48
|
match do |object|
|
45
49
|
@validators = []
|
46
50
|
|
47
51
|
model = object.is_a?(Class) ? object : object.class
|
48
52
|
|
49
|
-
DatabaseValidations::
|
50
|
-
|
53
|
+
model.validators.grep(DatabaseValidations::DbUniquenessValidator).each do |validator|
|
54
|
+
validator.attributes.each do |attribute|
|
51
55
|
@validators << {
|
52
|
-
field:
|
53
|
-
scope: validator.scope,
|
54
|
-
where: validator.
|
55
|
-
message: validator.message,
|
56
|
+
field: attribute,
|
57
|
+
scope: Array.wrap(validator.options[:scope]),
|
58
|
+
where: validator.where,
|
59
|
+
message: validator.options[:message],
|
56
60
|
index_name: validator.index_name,
|
57
|
-
case_sensitive: validator.case_sensitive
|
61
|
+
case_sensitive: validator.options[:case_sensitive]
|
58
62
|
}
|
59
63
|
end
|
60
64
|
end
|
61
65
|
|
66
|
+
case_sensitive_default = ActiveRecord::VERSION::MAJOR >= 6 ? nil : true
|
67
|
+
|
62
68
|
@validators.include?(
|
63
69
|
field: field,
|
64
70
|
scope: Array.wrap(@scope),
|
65
71
|
where: @where,
|
66
72
|
message: @message,
|
67
73
|
index_name: @index_name,
|
68
|
-
case_sensitive: @case_sensitive
|
74
|
+
case_sensitive: @case_sensitive.nil? ? case_sensitive_default : @case_sensitive
|
69
75
|
)
|
70
76
|
end
|
71
77
|
|
@@ -77,6 +83,7 @@ RSpec::Matchers.define :validate_db_uniqueness_of do |field| # rubocop:disable M
|
|
77
83
|
desc += "where: '#{@where}'; " if @where
|
78
84
|
desc += "index_name: '#{@index_name}'; " if @index_name
|
79
85
|
desc += 'be case insensitive.' unless @case_sensitive
|
86
|
+
desc += 'be case sensitive.' if @case_sensitive
|
80
87
|
desc
|
81
88
|
end
|
82
89
|
|
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.
|
4
|
+
version: 0.9.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Evgeniy Demin
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-09-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -16,20 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
20
|
-
- - "<"
|
21
|
-
- !ruby/object:Gem::Version
|
22
|
-
version: '6'
|
19
|
+
version: 4.2.0
|
23
20
|
type: :runtime
|
24
21
|
prerelease: false
|
25
22
|
version_requirements: !ruby/object:Gem::Requirement
|
26
23
|
requirements:
|
27
24
|
- - ">="
|
28
25
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
30
|
-
- - "<"
|
31
|
-
- !ruby/object:Gem::Version
|
32
|
-
version: '6'
|
26
|
+
version: 4.2.0
|
33
27
|
- !ruby/object:Gem::Dependency
|
34
28
|
name: benchmark-ips
|
35
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -50,56 +44,70 @@ dependencies:
|
|
50
44
|
requirements:
|
51
45
|
- - "~>"
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
47
|
+
version: '2.0'
|
54
48
|
type: :development
|
55
49
|
prerelease: false
|
56
50
|
version_requirements: !ruby/object:Gem::Requirement
|
57
51
|
requirements:
|
58
52
|
- - "~>"
|
59
53
|
- !ruby/object:Gem::Version
|
60
|
-
version: '
|
54
|
+
version: '2.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: db-query-matchers
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.9'
|
61
69
|
- !ruby/object:Gem::Dependency
|
62
70
|
name: mysql2
|
63
71
|
requirement: !ruby/object:Gem::Requirement
|
64
72
|
requirements:
|
65
|
-
- - "
|
73
|
+
- - ">="
|
66
74
|
- !ruby/object:Gem::Version
|
67
|
-
version: '0
|
75
|
+
version: '0'
|
68
76
|
type: :development
|
69
77
|
prerelease: false
|
70
78
|
version_requirements: !ruby/object:Gem::Requirement
|
71
79
|
requirements:
|
72
|
-
- - "
|
80
|
+
- - ">="
|
73
81
|
- !ruby/object:Gem::Version
|
74
|
-
version: '0
|
82
|
+
version: '0'
|
75
83
|
- !ruby/object:Gem::Dependency
|
76
84
|
name: pg
|
77
85
|
requirement: !ruby/object:Gem::Requirement
|
78
86
|
requirements:
|
79
|
-
- - "
|
87
|
+
- - ">="
|
80
88
|
- !ruby/object:Gem::Version
|
81
|
-
version: '
|
89
|
+
version: '0'
|
82
90
|
type: :development
|
83
91
|
prerelease: false
|
84
92
|
version_requirements: !ruby/object:Gem::Requirement
|
85
93
|
requirements:
|
86
|
-
- - "
|
94
|
+
- - ">="
|
87
95
|
- !ruby/object:Gem::Version
|
88
|
-
version: '
|
96
|
+
version: '0'
|
89
97
|
- !ruby/object:Gem::Dependency
|
90
98
|
name: rake
|
91
99
|
requirement: !ruby/object:Gem::Requirement
|
92
100
|
requirements:
|
93
101
|
- - "~>"
|
94
102
|
- !ruby/object:Gem::Version
|
95
|
-
version: '
|
103
|
+
version: '13.0'
|
96
104
|
type: :development
|
97
105
|
prerelease: false
|
98
106
|
version_requirements: !ruby/object:Gem::Requirement
|
99
107
|
requirements:
|
100
108
|
- - "~>"
|
101
109
|
- !ruby/object:Gem::Version
|
102
|
-
version: '
|
110
|
+
version: '13.0'
|
103
111
|
- !ruby/object:Gem::Dependency
|
104
112
|
name: rspec
|
105
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -175,16 +183,19 @@ files:
|
|
175
183
|
- lib/database_validations/lib/adapters/mysql_adapter.rb
|
176
184
|
- lib/database_validations/lib/adapters/postgresql_adapter.rb
|
177
185
|
- lib/database_validations/lib/adapters/sqlite_adapter.rb
|
178
|
-
- lib/database_validations/lib/
|
179
|
-
- lib/database_validations/lib/
|
180
|
-
- lib/database_validations/lib/
|
186
|
+
- lib/database_validations/lib/attribute_validator.rb
|
187
|
+
- lib/database_validations/lib/checkers/db_presence_validator.rb
|
188
|
+
- lib/database_validations/lib/checkers/db_uniqueness_validator.rb
|
181
189
|
- lib/database_validations/lib/errors.rb
|
182
|
-
- lib/database_validations/lib/
|
183
|
-
- lib/database_validations/lib/
|
190
|
+
- lib/database_validations/lib/injector.rb
|
191
|
+
- lib/database_validations/lib/key_generator.rb
|
192
|
+
- lib/database_validations/lib/presence_key_extractor.rb
|
184
193
|
- lib/database_validations/lib/rescuer.rb
|
185
|
-
- lib/database_validations/lib/
|
186
|
-
- lib/database_validations/lib/
|
187
|
-
- lib/database_validations/lib/
|
194
|
+
- lib/database_validations/lib/storage.rb
|
195
|
+
- lib/database_validations/lib/uniqueness_key_extractor.rb
|
196
|
+
- lib/database_validations/lib/validations.rb
|
197
|
+
- lib/database_validations/lib/validators/db_presence_validator.rb
|
198
|
+
- lib/database_validations/lib/validators/db_uniqueness_validator.rb
|
188
199
|
- lib/database_validations/rails/railtie.rb
|
189
200
|
- lib/database_validations/rspec/matchers.rb
|
190
201
|
- lib/database_validations/rspec/uniqueness_validator_matcher.rb
|
@@ -197,7 +208,7 @@ homepage: https://github.com/toptal/database_validations
|
|
197
208
|
licenses:
|
198
209
|
- MIT
|
199
210
|
metadata: {}
|
200
|
-
post_install_message:
|
211
|
+
post_install_message:
|
201
212
|
rdoc_options: []
|
202
213
|
require_paths:
|
203
214
|
- lib
|
@@ -212,9 +223,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
212
223
|
- !ruby/object:Gem::Version
|
213
224
|
version: '0'
|
214
225
|
requirements: []
|
215
|
-
|
216
|
-
|
217
|
-
signing_key:
|
226
|
+
rubygems_version: 3.0.8
|
227
|
+
signing_key:
|
218
228
|
specification_version: 4
|
219
229
|
summary: Provide compatibility between database constraints and ActiveRecord validations
|
220
230
|
with better performance and consistency.
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
module ClassMethods
|
3
|
-
def db_belongs_to(name, scope = nil, **options)
|
4
|
-
Helpers.cache_valid_method!(self)
|
5
|
-
|
6
|
-
@database_validations_storage ||= DatabaseValidations::OptionsStorage.new(self)
|
7
|
-
|
8
|
-
belongs_to(name, scope, options.merge(optional: true))
|
9
|
-
|
10
|
-
foreign_key = reflections[name.to_s].foreign_key
|
11
|
-
|
12
|
-
@database_validations_storage.push_belongs_to(foreign_key, name)
|
13
|
-
|
14
|
-
validates_with DatabaseValidations::DBPresenceValidator,
|
15
|
-
DatabaseValidations::BelongsToOptions.validator_options(name, foreign_key)
|
16
|
-
|
17
|
-
include(DatabaseValidations::Rescuer)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,39 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
class BelongsToOptions
|
3
|
-
def self.validator_options(association, foreign_key)
|
4
|
-
{ attributes: association, foreign_key: foreign_key, message: :required }
|
5
|
-
end
|
6
|
-
|
7
|
-
attr_reader :column, :adapter, :relation
|
8
|
-
|
9
|
-
def initialize(column, relation, adapter)
|
10
|
-
@column = column
|
11
|
-
@relation = relation
|
12
|
-
@adapter = adapter
|
13
|
-
|
14
|
-
raise_if_unsupported_database!
|
15
|
-
raise_if_foreign_key_missed! unless ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
|
16
|
-
end
|
17
|
-
|
18
|
-
# @return [String]
|
19
|
-
def key
|
20
|
-
@key ||= Helpers.generate_key_for_belongs_to(column)
|
21
|
-
end
|
22
|
-
|
23
|
-
def handle_foreign_key_error(instance)
|
24
|
-
# Hack to not query the database because we know the result already
|
25
|
-
instance.send("#{relation}=", nil)
|
26
|
-
instance.errors.add(relation, :blank, message: :required)
|
27
|
-
end
|
28
|
-
|
29
|
-
private
|
30
|
-
|
31
|
-
def raise_if_foreign_key_missed!
|
32
|
-
raise Errors::ForeignKeyNotFound.new(column, adapter.foreign_keys) unless adapter.find_foreign_key_by_column(column)
|
33
|
-
end
|
34
|
-
|
35
|
-
def raise_if_unsupported_database!
|
36
|
-
raise Errors::UnsupportedDatabase.new(:db_belongs_to, adapter.adapter_name) if adapter.adapter_name == :sqlite3
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
class DBPresenceValidator < ActiveRecord::Validations::PresenceValidator
|
3
|
-
# This is a hack to simulate presence validator
|
4
|
-
# It's used for cases when some 3rd parties are relies on the validators
|
5
|
-
# For example, +required+ option from simple_form checks the validator
|
6
|
-
def self.kind
|
7
|
-
:presence
|
8
|
-
end
|
9
|
-
|
10
|
-
attr_reader :foreign_key, :association
|
11
|
-
|
12
|
-
def initialize(options)
|
13
|
-
super(options)
|
14
|
-
|
15
|
-
@association = attributes.first
|
16
|
-
@foreign_key = options[:foreign_key]
|
17
|
-
end
|
18
|
-
|
19
|
-
# The else statement required only for optional: false
|
20
|
-
def validate(record)
|
21
|
-
if record._database_validations_fallback
|
22
|
-
super
|
23
|
-
else
|
24
|
-
return unless record.public_send(foreign_key).blank? && record.public_send(association).blank?
|
25
|
-
|
26
|
-
record.errors.add(association, :blank, message: :required)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
@@ -1,79 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
module Helpers
|
3
|
-
module_function
|
4
|
-
|
5
|
-
def cache_valid_method!(klass)
|
6
|
-
return if klass.method_defined?(:valid_without_database_validations?)
|
7
|
-
|
8
|
-
klass.alias_method(:valid_without_database_validations?, :valid?)
|
9
|
-
end
|
10
|
-
|
11
|
-
def handle_error!(instance, error)
|
12
|
-
case error
|
13
|
-
when ActiveRecord::RecordNotUnique
|
14
|
-
handle_unique_error!(instance, error)
|
15
|
-
when ActiveRecord::InvalidForeignKey
|
16
|
-
handle_foreign_key_error!(instance, error)
|
17
|
-
else false
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def handle_unique_error!(instance, error)
|
22
|
-
adapter = Adapters.factory(instance.class)
|
23
|
-
|
24
|
-
keys = [
|
25
|
-
generate_key_for_uniqueness_index(adapter.unique_index_name(error.message)),
|
26
|
-
generate_key_for_uniqueness(adapter.unique_error_columns(error.message))
|
27
|
-
]
|
28
|
-
|
29
|
-
each_options_storage(instance.class) do |storage|
|
30
|
-
keys.each { |key| return storage[key].handle_unique_error(instance) if storage[key] }
|
31
|
-
end
|
32
|
-
|
33
|
-
false
|
34
|
-
end
|
35
|
-
|
36
|
-
def handle_foreign_key_error!(instance, error)
|
37
|
-
adapter = Adapters.factory(instance.class)
|
38
|
-
column_key = generate_key_for_belongs_to(adapter.foreign_key_error_column(error.message))
|
39
|
-
|
40
|
-
each_options_storage(instance.class) do |storage|
|
41
|
-
return storage[column_key].handle_foreign_key_error(instance) if storage[column_key]
|
42
|
-
end
|
43
|
-
|
44
|
-
false
|
45
|
-
end
|
46
|
-
|
47
|
-
def each_options_storage(klass)
|
48
|
-
while klass.respond_to?(:validates_db_uniqueness_of)
|
49
|
-
storage = storage_of(klass)
|
50
|
-
yield(storage) if storage
|
51
|
-
klass = klass.superclass
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def storage_of(klass)
|
56
|
-
klass.instance_variable_get(:'@database_validations_storage')
|
57
|
-
end
|
58
|
-
|
59
|
-
def unify_columns(*args)
|
60
|
-
args.flatten.compact.map(&:to_s).sort
|
61
|
-
end
|
62
|
-
|
63
|
-
def generate_key_for_uniqueness_index(index_name)
|
64
|
-
generate_key(:uniqueness_index, index_name)
|
65
|
-
end
|
66
|
-
|
67
|
-
def generate_key_for_uniqueness(*columns)
|
68
|
-
generate_key(:uniqueness, columns)
|
69
|
-
end
|
70
|
-
|
71
|
-
def generate_key_for_belongs_to(column)
|
72
|
-
generate_key(:belongs_to, column)
|
73
|
-
end
|
74
|
-
|
75
|
-
def generate_key(type, *args)
|
76
|
-
[type, *unify_columns(args)].join('__')
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
class OptionsStorage
|
3
|
-
def initialize(klass)
|
4
|
-
@adapter = Adapters.factory(klass).new(klass)
|
5
|
-
@storage = {}
|
6
|
-
end
|
7
|
-
|
8
|
-
def push_uniqueness(field, options)
|
9
|
-
uniqueness_options = UniquenessOptions.new(field, options, adapter)
|
10
|
-
storage[uniqueness_options.index_key] = uniqueness_options
|
11
|
-
storage[uniqueness_options.column_key] = uniqueness_options
|
12
|
-
end
|
13
|
-
|
14
|
-
def push_belongs_to(field, relation)
|
15
|
-
belongs_to_options = BelongsToOptions.new(field, relation, adapter)
|
16
|
-
storage[belongs_to_options.key] = belongs_to_options
|
17
|
-
end
|
18
|
-
|
19
|
-
def [](key)
|
20
|
-
storage[key]
|
21
|
-
end
|
22
|
-
|
23
|
-
def options
|
24
|
-
storage.values
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
attr_reader :storage, :adapter
|
30
|
-
end
|
31
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
class DBUniquenessValidator < ActiveRecord::Validations::UniquenessValidator
|
3
|
-
# This is a hack to simulate presence validator
|
4
|
-
# It's used for cases when some 3rd parties are relies on the validators
|
5
|
-
# For example, +required+ option from simple_form checks the validator
|
6
|
-
def self.kind
|
7
|
-
:uniqueness
|
8
|
-
end
|
9
|
-
|
10
|
-
def validate(record)
|
11
|
-
super if record._database_validations_fallback
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
module ClassMethods
|
3
|
-
def validates_db_uniqueness_of(*attributes)
|
4
|
-
Helpers.cache_valid_method!(self)
|
5
|
-
|
6
|
-
@database_validations_storage ||= DatabaseValidations::OptionsStorage.new(self)
|
7
|
-
|
8
|
-
options = attributes.extract_options!
|
9
|
-
|
10
|
-
attributes.each do |attribute|
|
11
|
-
@database_validations_storage.push_uniqueness(attribute, options.merge(attributes: attribute))
|
12
|
-
end
|
13
|
-
|
14
|
-
validates_with DatabaseValidations::DBUniquenessValidator,
|
15
|
-
DatabaseValidations::UniquenessOptions.validator_options(attributes, options)
|
16
|
-
|
17
|
-
include(DatabaseValidations::Rescuer)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
class UniquenessOptions
|
3
|
-
CUSTOM_OPTIONS = %i[where index_name].freeze
|
4
|
-
DEFAULT_OPTIONS = { allow_nil: true, case_sensitive: true, allow_blank: false }.freeze
|
5
|
-
|
6
|
-
def self.validator_options(attributes, options)
|
7
|
-
DEFAULT_OPTIONS
|
8
|
-
.merge(attributes: attributes)
|
9
|
-
.merge(options)
|
10
|
-
.except(*CUSTOM_OPTIONS)
|
11
|
-
.tap { |opts| opts[:conditions] = -> { where(options[:where]) } if options[:where] }
|
12
|
-
end
|
13
|
-
|
14
|
-
attr_reader :field, :calculated_index_name
|
15
|
-
|
16
|
-
def initialize(field, options, adapter)
|
17
|
-
@field = field
|
18
|
-
@options = options
|
19
|
-
@adapter = adapter
|
20
|
-
|
21
|
-
raise_if_unsupported_options!
|
22
|
-
|
23
|
-
return if ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
|
24
|
-
|
25
|
-
index = responsible_index
|
26
|
-
raise_if_index_missed!(index)
|
27
|
-
|
28
|
-
@calculated_index_name = index.name
|
29
|
-
end
|
30
|
-
|
31
|
-
def handle_unique_error(instance)
|
32
|
-
error_options = options.except(:case_sensitive, :scope, :conditions, :attributes, *CUSTOM_OPTIONS)
|
33
|
-
error_options[:value] = instance.public_send(options[:attributes])
|
34
|
-
|
35
|
-
instance.errors.add(options[:attributes], :taken, error_options)
|
36
|
-
end
|
37
|
-
|
38
|
-
# @return [String]
|
39
|
-
def index_key
|
40
|
-
@index_key ||= Helpers.generate_key_for_uniqueness_index(index_name || calculated_index_name)
|
41
|
-
end
|
42
|
-
|
43
|
-
# @return [String]
|
44
|
-
def column_key
|
45
|
-
@column_key ||= Helpers.generate_key_for_uniqueness(columns)
|
46
|
-
end
|
47
|
-
|
48
|
-
# @return [Array<String>]
|
49
|
-
def columns
|
50
|
-
@columns ||= Helpers.unify_columns(field, scope)
|
51
|
-
end
|
52
|
-
|
53
|
-
# @return [String|nil]
|
54
|
-
def where_clause
|
55
|
-
@where_clause ||= options[:where]
|
56
|
-
end
|
57
|
-
|
58
|
-
# @return [String|nil]
|
59
|
-
def message
|
60
|
-
@message ||= options[:message]
|
61
|
-
end
|
62
|
-
|
63
|
-
# @return [Array<String|Symbol>]
|
64
|
-
def scope
|
65
|
-
@scope ||= Array.wrap(options[:scope])
|
66
|
-
end
|
67
|
-
|
68
|
-
# @return [String|Symbol|nil]
|
69
|
-
def index_name
|
70
|
-
@index_name ||= options[:index_name]
|
71
|
-
end
|
72
|
-
|
73
|
-
# @return [Boolean|nil]
|
74
|
-
def case_sensitive
|
75
|
-
@case_sensitive ||= options[:case_sensitive]
|
76
|
-
end
|
77
|
-
|
78
|
-
private
|
79
|
-
|
80
|
-
attr_reader :adapter, :options
|
81
|
-
|
82
|
-
def raise_if_unsupported_options!
|
83
|
-
options.except(:attributes).each_key do |option|
|
84
|
-
unless adapter.support_option?(option)
|
85
|
-
raise Errors::OptionIsNotSupported.new(option, adapter.adapter_name, adapter.supported_options)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
89
|
-
|
90
|
-
def responsible_index
|
91
|
-
index_name ? adapter.find_index_by_name(index_name.to_s) : adapter.find_index(columns, where_clause)
|
92
|
-
end
|
93
|
-
|
94
|
-
def raise_if_index_missed!(index)
|
95
|
-
raise Errors::IndexNotFound.new(columns, where_clause, index_name, adapter.indexes, adapter.table_name) unless index
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|