database_validations 0.8.5 → 0.9.1
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 +14 -10
- data/lib/database_validations/lib/adapters.rb +20 -0
- data/lib/database_validations/{validations → lib}/adapters/base_adapter.rb +1 -17
- data/lib/database_validations/lib/adapters/mysql_adapter.rb +19 -0
- data/lib/database_validations/lib/adapters/postgresql_adapter.rb +21 -0
- data/lib/database_validations/lib/adapters/sqlite_adapter.rb +19 -0
- 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/{validations → lib}/errors.rb +1 -12
- 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 +36 -0
- 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 +63 -0
- data/lib/database_validations/lib/validators/db_uniqueness_validator.rb +48 -0
- data/lib/database_validations/rspec/uniqueness_validator_matcher.rb +19 -10
- data/lib/database_validations/version.rb +1 -1
- metadata +52 -41
- data/lib/database_validations/validations/adapters.rb +0 -20
- data/lib/database_validations/validations/adapters/mysql_adapter.rb +0 -20
- data/lib/database_validations/validations/adapters/postgresql_adapter.rb +0 -20
- data/lib/database_validations/validations/adapters/sqlite_adapter.rb +0 -22
- data/lib/database_validations/validations/belongs_to_handlers.rb +0 -58
- data/lib/database_validations/validations/belongs_to_options.rb +0 -44
- data/lib/database_validations/validations/belongs_to_presence_validator.rb +0 -25
- data/lib/database_validations/validations/helpers.rb +0 -69
- data/lib/database_validations/validations/options_storage.rb +0 -34
- data/lib/database_validations/validations/uniqueness_handlers.rb +0 -56
- data/lib/database_validations/validations/uniqueness_options.rb +0 -104
- data/lib/database_validations/validations/valid_without_database_validations.rb +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7611b039f9baa05f65bee0a18cbfc2cfe2c0ae5e7f69bc01d232b00de5306f92
|
4
|
+
data.tar.gz: 9601fd3196555c0d861b5fb1a3809d25814453db9199fbcc0b115b7976a3012d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8a72b136bdd5528cf93dc019c80ecf88b09a9484a68d523e5574e28ee7ffb122122eda7cf3062655015f59637d4dd06de4c3c028c79067fea101b326f15a80e6
|
7
|
+
data.tar.gz: bb9ee57860b06c58853db54583b08acd953f0cdf037d0e11a5faee3b72aa748b022051b3ee21a8fbe3d95f18f7c460c5a1d91e77cbbd2bdf5d4237802b9d8ad4
|
data/lib/database_validations.rb
CHANGED
@@ -4,18 +4,22 @@ require 'database_validations/version'
|
|
4
4
|
|
5
5
|
require 'database_validations/rails/railtie' if defined?(Rails)
|
6
6
|
|
7
|
-
require 'database_validations/
|
8
|
-
require 'database_validations/
|
7
|
+
require 'database_validations/lib/checkers/db_uniqueness_validator'
|
8
|
+
require 'database_validations/lib/checkers/db_presence_validator'
|
9
9
|
|
10
|
-
require 'database_validations/
|
11
|
-
require 'database_validations/
|
12
|
-
require 'database_validations/validations/belongs_to_options'
|
10
|
+
require 'database_validations/lib/validators/db_uniqueness_validator'
|
11
|
+
require 'database_validations/lib/validators/db_presence_validator'
|
13
12
|
|
14
|
-
require 'database_validations/
|
15
|
-
require 'database_validations/
|
16
|
-
require 'database_validations/
|
17
|
-
require 'database_validations/
|
18
|
-
require 'database_validations/
|
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'
|
19
|
+
require 'database_validations/lib/errors'
|
20
|
+
require 'database_validations/lib/rescuer'
|
21
|
+
require 'database_validations/lib/injector'
|
22
|
+
require 'database_validations/lib/adapters'
|
19
23
|
|
20
24
|
module DatabaseValidations
|
21
25
|
extend ActiveSupport::Concern
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'database_validations/lib/adapters/base_adapter'
|
2
|
+
require 'database_validations/lib/adapters/sqlite_adapter'
|
3
|
+
require 'database_validations/lib/adapters/postgresql_adapter'
|
4
|
+
require 'database_validations/lib/adapters/mysql_adapter'
|
5
|
+
|
6
|
+
module DatabaseValidations
|
7
|
+
module Adapters
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def factory(model)
|
11
|
+
case (database = model.connection_config[:adapter].downcase.to_sym)
|
12
|
+
when SqliteAdapter::ADAPTER then SqliteAdapter
|
13
|
+
when PostgresqlAdapter::ADAPTER then PostgresqlAdapter
|
14
|
+
when MysqlAdapter::ADAPTER then MysqlAdapter
|
15
|
+
else
|
16
|
+
raise Errors::UnknownDatabase, database
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -28,23 +28,7 @@ module DatabaseValidations
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def find_foreign_key_by_column(column)
|
31
|
-
|
32
|
-
end
|
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
|
31
|
+
foreign_keys.find { |foreign_key| foreign_key.column.to_s == column.to_s }
|
48
32
|
end
|
49
33
|
|
50
34
|
# @return [String]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Adapters
|
3
|
+
class MysqlAdapter < BaseAdapter
|
4
|
+
ADAPTER = :mysql2
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def unique_index_name(error_message)
|
8
|
+
error_message[/key '([^']+)'/, 1]&.split('.')&.last
|
9
|
+
end
|
10
|
+
|
11
|
+
def unique_error_columns(_error_message); end
|
12
|
+
|
13
|
+
def foreign_key_error_column(error_message)
|
14
|
+
error_message[/FOREIGN KEY \(`([^`]+)`\)/, 1]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Adapters
|
3
|
+
class PostgresqlAdapter < BaseAdapter
|
4
|
+
ADAPTER = :postgresql
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def unique_index_name(error_message)
|
8
|
+
error_message[/unique constraint "([^"]+)"/, 1]
|
9
|
+
end
|
10
|
+
|
11
|
+
def unique_error_columns(error_message)
|
12
|
+
error_message[/Key \((.+)\)=/, 1].split(', ')
|
13
|
+
end
|
14
|
+
|
15
|
+
def foreign_key_error_column(error_message)
|
16
|
+
error_message[/Key \(([^)]+)\)/, 1]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Adapters
|
3
|
+
class SqliteAdapter < BaseAdapter
|
4
|
+
ADAPTER = :sqlite3
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def unique_index_name(_error_message); end
|
8
|
+
|
9
|
+
def unique_error_columns(error_message)
|
10
|
+
error_message.scan(/\w+\.([^,:]+)/).flatten
|
11
|
+
end
|
12
|
+
|
13
|
+
def foreign_key_error_column(error_message)
|
14
|
+
error_message[/\("([^"]+)"\) VALUES/, 1]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
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
|
|
@@ -59,7 +48,7 @@ module DatabaseValidations
|
|
59
48
|
@column = column
|
60
49
|
@foreign_keys = foreign_keys
|
61
50
|
|
62
|
-
super "No foreign key found with column: \"#{column}\".
|
51
|
+
super "No foreign key found with column: \"#{column}\". Found foreign keys are: #{foreign_keys}. " + env_message
|
63
52
|
end
|
64
53
|
end
|
65
54
|
|
@@ -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
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
module Rescuer
|
3
|
+
module_function
|
4
|
+
|
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
|
15
|
+
end
|
16
|
+
|
17
|
+
def process(instance, error, key_types)
|
18
|
+
adapter = Adapters.factory(instance.class)
|
19
|
+
|
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
|
+
if attribute_validator
|
28
|
+
attribute_validator.validator.apply_error(instance, attribute_validator.attribute)
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
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
|