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
@@ -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,63 @@
|
|
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
|
+
# TODO: add support of optional db_belongs_to
|
25
|
+
def validate(record)
|
26
|
+
if record._database_validations_fallback
|
27
|
+
super
|
28
|
+
else
|
29
|
+
attributes.each do |attribute|
|
30
|
+
reflection = record.class._reflect_on_association(attribute)
|
31
|
+
|
32
|
+
next if reflection && record.public_send(reflection.foreign_key).present?
|
33
|
+
|
34
|
+
validate_each(record, attribute, record.public_send(attribute))
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def apply_error(instance, attribute)
|
40
|
+
# Helps to avoid querying the database when attribute is association
|
41
|
+
instance.send("#{attribute}=", nil)
|
42
|
+
instance.errors.add(attribute, :blank, message: REFLECTION_MESSAGE)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module ClassMethods
|
47
|
+
def validates_db_presence_of(*attr_names)
|
48
|
+
validates_with(DatabaseValidations::DbPresenceValidator, _merge_attributes(attr_names))
|
49
|
+
end
|
50
|
+
|
51
|
+
def db_belongs_to(name, scope = nil, **options)
|
52
|
+
if ActiveRecord::VERSION::MAJOR < 5
|
53
|
+
options[:required] = false
|
54
|
+
else
|
55
|
+
options[:optional] = true
|
56
|
+
end
|
57
|
+
|
58
|
+
belongs_to(name, scope, options)
|
59
|
+
|
60
|
+
validates_with DatabaseValidations::DbPresenceValidator, _merge_attributes([name, message: DatabaseValidations::DbPresenceValidator::REFLECTION_MESSAGE]) # rubocop:disable Metrics/LineLength
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module DatabaseValidations
|
2
|
+
class DbUniquenessValidator < ActiveRecord::Validations::UniquenessValidator
|
3
|
+
attr_reader :index_name, :where, :klass
|
4
|
+
|
5
|
+
# Used to make 3rd party libraries work correctly
|
6
|
+
#
|
7
|
+
# @return [Symbol]
|
8
|
+
def self.kind
|
9
|
+
:uniqueness
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param [Hash] options
|
13
|
+
def initialize(options)
|
14
|
+
options[:allow_nil] = true
|
15
|
+
options[:allow_blank] = false
|
16
|
+
|
17
|
+
if options.key?(:where)
|
18
|
+
condition = options[:where]
|
19
|
+
options[:conditions] = -> { where(condition) }
|
20
|
+
end
|
21
|
+
|
22
|
+
@index_name = options.delete(:index_name) if options.key?(:index_name)
|
23
|
+
@where = options.delete(:where) if options.key?(:where)
|
24
|
+
|
25
|
+
super
|
26
|
+
|
27
|
+
Injector.inject(klass)
|
28
|
+
Checkers::DbUniquenessValidator.validate!(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def validate(record)
|
32
|
+
super if record._database_validations_fallback
|
33
|
+
end
|
34
|
+
|
35
|
+
def apply_error(instance, attribute)
|
36
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
37
|
+
error_options[:value] = instance.public_send(attribute)
|
38
|
+
|
39
|
+
instance.errors.add(attribute, :taken, error_options)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
module ClassMethods
|
44
|
+
def validates_db_uniqueness_of(*attr_names)
|
45
|
+
validates_with(DatabaseValidations::DbUniquenessValidator, _merge_attributes(attr_names))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -41,29 +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
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
53
|
+
model.validators.grep(DatabaseValidations::DbUniquenessValidator).each do |validator|
|
54
|
+
validator.attributes.each do |attribute|
|
55
|
+
@validators << {
|
56
|
+
field: attribute,
|
57
|
+
scope: Array.wrap(validator.options[:scope]),
|
58
|
+
where: validator.where,
|
59
|
+
message: validator.options[:message],
|
60
|
+
index_name: validator.index_name,
|
61
|
+
case_sensitive: validator.options[:case_sensitive]
|
62
|
+
}
|
63
|
+
end
|
58
64
|
end
|
59
65
|
|
66
|
+
case_sensitive_default = ActiveRecord::VERSION::MAJOR >= 6 ? nil : true
|
67
|
+
|
60
68
|
@validators.include?(
|
61
69
|
field: field,
|
62
70
|
scope: Array.wrap(@scope),
|
63
71
|
where: @where,
|
64
72
|
message: @message,
|
65
73
|
index_name: @index_name,
|
66
|
-
case_sensitive: @case_sensitive
|
74
|
+
case_sensitive: @case_sensitive.nil? ? case_sensitive_default : @case_sensitive
|
67
75
|
)
|
68
76
|
end
|
69
77
|
|
@@ -75,6 +83,7 @@ RSpec::Matchers.define :validate_db_uniqueness_of do |field| # rubocop:disable M
|
|
75
83
|
desc += "where: '#{@where}'; " if @where
|
76
84
|
desc += "index_name: '#{@index_name}'; " if @index_name
|
77
85
|
desc += 'be case insensitive.' unless @case_sensitive
|
86
|
+
desc += 'be case sensitive.' if @case_sensitive
|
78
87
|
desc
|
79
88
|
end
|
80
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.1
|
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-06-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
|
@@ -170,6 +178,24 @@ extensions: []
|
|
170
178
|
extra_rdoc_files: []
|
171
179
|
files:
|
172
180
|
- lib/database_validations.rb
|
181
|
+
- lib/database_validations/lib/adapters.rb
|
182
|
+
- lib/database_validations/lib/adapters/base_adapter.rb
|
183
|
+
- lib/database_validations/lib/adapters/mysql_adapter.rb
|
184
|
+
- lib/database_validations/lib/adapters/postgresql_adapter.rb
|
185
|
+
- lib/database_validations/lib/adapters/sqlite_adapter.rb
|
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
|
189
|
+
- lib/database_validations/lib/errors.rb
|
190
|
+
- lib/database_validations/lib/injector.rb
|
191
|
+
- lib/database_validations/lib/key_generator.rb
|
192
|
+
- lib/database_validations/lib/presence_key_extractor.rb
|
193
|
+
- lib/database_validations/lib/rescuer.rb
|
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
|
173
199
|
- lib/database_validations/rails/railtie.rb
|
174
200
|
- lib/database_validations/rspec/matchers.rb
|
175
201
|
- lib/database_validations/rspec/uniqueness_validator_matcher.rb
|
@@ -177,26 +203,12 @@ files:
|
|
177
203
|
- lib/database_validations/rubocop/cop/uniqueness_of.rb
|
178
204
|
- lib/database_validations/rubocop/cops.rb
|
179
205
|
- lib/database_validations/tasks/database_validations.rake
|
180
|
-
- lib/database_validations/validations/adapters.rb
|
181
|
-
- lib/database_validations/validations/adapters/base_adapter.rb
|
182
|
-
- lib/database_validations/validations/adapters/mysql_adapter.rb
|
183
|
-
- lib/database_validations/validations/adapters/postgresql_adapter.rb
|
184
|
-
- lib/database_validations/validations/adapters/sqlite_adapter.rb
|
185
|
-
- lib/database_validations/validations/belongs_to_handlers.rb
|
186
|
-
- lib/database_validations/validations/belongs_to_options.rb
|
187
|
-
- lib/database_validations/validations/belongs_to_presence_validator.rb
|
188
|
-
- lib/database_validations/validations/errors.rb
|
189
|
-
- lib/database_validations/validations/helpers.rb
|
190
|
-
- lib/database_validations/validations/options_storage.rb
|
191
|
-
- lib/database_validations/validations/uniqueness_handlers.rb
|
192
|
-
- lib/database_validations/validations/uniqueness_options.rb
|
193
|
-
- lib/database_validations/validations/valid_without_database_validations.rb
|
194
206
|
- lib/database_validations/version.rb
|
195
207
|
homepage: https://github.com/toptal/database_validations
|
196
208
|
licenses:
|
197
209
|
- MIT
|
198
210
|
metadata: {}
|
199
|
-
post_install_message:
|
211
|
+
post_install_message:
|
200
212
|
rdoc_options: []
|
201
213
|
require_paths:
|
202
214
|
- lib
|
@@ -211,9 +223,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
211
223
|
- !ruby/object:Gem::Version
|
212
224
|
version: '0'
|
213
225
|
requirements: []
|
214
|
-
|
215
|
-
|
216
|
-
signing_key:
|
226
|
+
rubygems_version: 3.1.2
|
227
|
+
signing_key:
|
217
228
|
specification_version: 4
|
218
229
|
summary: Provide compatibility between database constraints and ActiveRecord validations
|
219
230
|
with better performance and consistency.
|
@@ -1,20 +0,0 @@
|
|
1
|
-
require 'database_validations/validations/adapters/base_adapter'
|
2
|
-
require 'database_validations/validations/adapters/sqlite_adapter'
|
3
|
-
require 'database_validations/validations/adapters/postgresql_adapter'
|
4
|
-
require 'database_validations/validations/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.new(model)
|
13
|
-
when PostgresqlAdapter::ADAPTER then PostgresqlAdapter.new(model)
|
14
|
-
when MysqlAdapter::ADAPTER then MysqlAdapter.new(model)
|
15
|
-
else
|
16
|
-
raise Errors::UnknownDatabase, database
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
module Adapters
|
3
|
-
class MysqlAdapter < BaseAdapter
|
4
|
-
SUPPORTED_OPTIONS = %i[scope message if unless index_name].freeze
|
5
|
-
ADAPTER = :mysql2
|
6
|
-
|
7
|
-
def index_name(error_message)
|
8
|
-
error_message[/key '([^']+)'/, 1]
|
9
|
-
end
|
10
|
-
|
11
|
-
def unique_error_columns(error_message)
|
12
|
-
find_index_by_name(index_name(error_message)).columns
|
13
|
-
end
|
14
|
-
|
15
|
-
def foreign_key_error_column(error_message)
|
16
|
-
error_message[/FOREIGN KEY \(`([^`]+)`\)/, 1]
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
@@ -1,20 +0,0 @@
|
|
1
|
-
module DatabaseValidations
|
2
|
-
module Adapters
|
3
|
-
class PostgresqlAdapter < BaseAdapter
|
4
|
-
SUPPORTED_OPTIONS = %i[scope message where if unless index_name case_sensitive].freeze
|
5
|
-
ADAPTER = :postgresql
|
6
|
-
|
7
|
-
def index_name(error_message)
|
8
|
-
error_message[/unique constraint "([^"]+)"/, 1]
|
9
|
-
end
|
10
|
-
|
11
|
-
def unique_error_columns(error_message)
|
12
|
-
find_index_by_name(index_name(error_message)).columns
|
13
|
-
end
|
14
|
-
|
15
|
-
def foreign_key_error_column(error_message)
|
16
|
-
error_message[/Key \(([^)]+)\)/, 1]
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|