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.
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