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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/database_validations.rb +14 -10
  3. data/lib/database_validations/lib/adapters.rb +20 -0
  4. data/lib/database_validations/{validations → lib}/adapters/base_adapter.rb +1 -17
  5. data/lib/database_validations/lib/adapters/mysql_adapter.rb +19 -0
  6. data/lib/database_validations/lib/adapters/postgresql_adapter.rb +21 -0
  7. data/lib/database_validations/lib/adapters/sqlite_adapter.rb +19 -0
  8. data/lib/database_validations/lib/attribute_validator.rb +3 -0
  9. data/lib/database_validations/lib/checkers/db_presence_validator.rb +36 -0
  10. data/lib/database_validations/lib/checkers/db_uniqueness_validator.rb +47 -0
  11. data/lib/database_validations/{validations → lib}/errors.rb +1 -12
  12. data/lib/database_validations/lib/injector.rb +13 -0
  13. data/lib/database_validations/lib/key_generator.rb +32 -0
  14. data/lib/database_validations/lib/presence_key_extractor.rb +18 -0
  15. data/lib/database_validations/lib/rescuer.rb +36 -0
  16. data/lib/database_validations/lib/storage.rb +28 -0
  17. data/lib/database_validations/lib/uniqueness_key_extractor.rb +38 -0
  18. data/lib/database_validations/lib/validations.rb +31 -0
  19. data/lib/database_validations/lib/validators/db_presence_validator.rb +63 -0
  20. data/lib/database_validations/lib/validators/db_uniqueness_validator.rb +48 -0
  21. data/lib/database_validations/rspec/uniqueness_validator_matcher.rb +19 -10
  22. data/lib/database_validations/version.rb +1 -1
  23. metadata +52 -41
  24. data/lib/database_validations/validations/adapters.rb +0 -20
  25. data/lib/database_validations/validations/adapters/mysql_adapter.rb +0 -20
  26. data/lib/database_validations/validations/adapters/postgresql_adapter.rb +0 -20
  27. data/lib/database_validations/validations/adapters/sqlite_adapter.rb +0 -22
  28. data/lib/database_validations/validations/belongs_to_handlers.rb +0 -58
  29. data/lib/database_validations/validations/belongs_to_options.rb +0 -44
  30. data/lib/database_validations/validations/belongs_to_presence_validator.rb +0 -25
  31. data/lib/database_validations/validations/helpers.rb +0 -69
  32. data/lib/database_validations/validations/options_storage.rb +0 -34
  33. data/lib/database_validations/validations/uniqueness_handlers.rb +0 -56
  34. data/lib/database_validations/validations/uniqueness_options.rb +0 -104
  35. 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::Helpers.each_uniqueness_validator(model) do |validator|
50
- @validators << {
51
- field: validator.field,
52
- scope: validator.scope,
53
- where: validator.where_clause,
54
- message: validator.message,
55
- index_name: validator.index_name,
56
- case_sensitive: validator.case_sensitive
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
 
@@ -1,3 +1,3 @@
1
1
  module DatabaseValidations
2
- VERSION = '0.8.5'.freeze
2
+ VERSION = '0.9.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: database_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.5
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: 2019-02-06 00:00:00.000000000 Z
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: '3.2'
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: '3.2'
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: '1.16'
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: '1.16'
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.5'
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.5'
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: '1.1'
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: '1.1'
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: '10.0'
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: '10.0'
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
- rubyforge_project:
215
- rubygems_version: 2.7.8
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