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