database_validations 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 70578fbf45115b1b0a349f2676b2c1a689f2aa8d29da2da189da263306c0b333
4
- data.tar.gz: f6fc66d339e488cccdf750103637543ecd39d34a0a84366efa2382addcde6aee
3
+ metadata.gz: 2e13dbc2023a631be57aaacd12ec2c75ca9b7b355e7015737df4b760713b5756
4
+ data.tar.gz: 8b84441e52646fba283120a8e33d40bdb43162f31f202e2cd07e6f5fa6dbb03e
5
5
  SHA512:
6
- metadata.gz: 6d40cf1b7d776cf984846f9cef85f1969271d619ae4ee9b9127fe67307c8983eb0ce385883541394932d9cc1f88a2e69fccdf5ff6b800ee06c5a1f5710b21495
7
- data.tar.gz: 1e94c35ae4a40a56e275b6bad79a05cf0c3f29b735cf37495c2fb5e9013d2929ab0a54f025c6221a5fa8179a26d3660c9518ba8d5c1f4310ea3832b4ab4cdb28
6
+ metadata.gz: 324f867954cf0b2660431e6da9b5851f3ccae84b6ff2e5d10de6149b336387adc7c07a4e295748c75663a18822436c88578c6d246e869238f56d0e3563bc1f2e
7
+ data.tar.gz: 3d99ab395db1b3b493e372c22674283cd7822bd6de89cbcd71b08218cfec96eee709538dd89a872977edd936434564798a20e1af8e837c1f886fbd3a9f8e345c
@@ -8,7 +8,13 @@ module DatabaseValidations
8
8
  module_function
9
9
 
10
10
  def factory(model)
11
- case (database = model.connection_config[:adapter].downcase.to_sym)
11
+ database = if ActiveRecord.version < Gem::Version.new('6.1.0')
12
+ model.connection_config[:adapter].downcase.to_sym
13
+ else
14
+ model.connection_db_config.adapter.downcase.to_sym
15
+ end
16
+
17
+ case database
12
18
  when SqliteAdapter::ADAPTER then SqliteAdapter
13
19
  when PostgresqlAdapter::ADAPTER then PostgresqlAdapter
14
20
  when MysqlAdapter::ADAPTER then MysqlAdapter
@@ -9,18 +9,25 @@ module DatabaseValidations
9
9
  end
10
10
 
11
11
  # @param [String] index_name
12
- def find_index_by_name(index_name)
13
- indexes.find { |index| index.name == index_name }
12
+ def find_unique_index_by_name(index_name)
13
+ unique_indexes.find { |index| index.name == index_name }
14
14
  end
15
15
 
16
16
  # @param [Array<String>] columns
17
17
  # @param [String] where
18
- def find_index(columns, where)
19
- indexes.find { |index| Array.wrap(index.columns).map(&:to_s).sort == columns && index.where == where }
18
+ def find_unique_index(columns, where)
19
+ unique_indexes.find { |index| Array.wrap(index.columns).map(&:to_s).sort == columns && index.where == where }
20
20
  end
21
21
 
22
- def indexes
23
- model.connection.indexes(model.table_name).select(&:unique)
22
+ def unique_indexes
23
+ connection = model.connection
24
+
25
+ if connection.schema_cache.respond_to?(:indexes)
26
+ # Rails 6 only
27
+ connection.schema_cache.indexes(model.table_name).select(&:unique)
28
+ else
29
+ connection.indexes(model.table_name).select(&:unique)
30
+ end
24
31
  end
25
32
 
26
33
  def foreign_keys
@@ -5,7 +5,7 @@ module DatabaseValidations
5
5
 
6
6
  class << self
7
7
  def unique_index_name(error_message)
8
- error_message[/key '([^']+)'/, 1]
8
+ error_message[/key '([^']+)'/, 1]&.split('.')&.last
9
9
  end
10
10
 
11
11
  def unique_error_columns(_error_message); end
@@ -38,8 +38,8 @@ module DatabaseValidations
38
38
 
39
39
  validator.attributes.map do |attribute|
40
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
41
+ index = validator.index_name ? adapter.find_unique_index_by_name(validator.index_name.to_s) : adapter.find_unique_index(columns, validator.where) # rubocop:disable Metrics/LineLength
42
+ raise Errors::IndexNotFound.new(columns, validator.where, validator.index_name, adapter.unique_indexes, adapter.table_name) unless index && valid_index?(columns, index) # rubocop:disable Metrics/LineLength
43
43
  end
44
44
  end
45
45
  end
@@ -24,13 +24,19 @@ module DatabaseValidations
24
24
  keys.each do |key|
25
25
  attribute_validator = instance._db_validators[key]
26
26
 
27
- if attribute_validator
28
- attribute_validator.validator.apply_error(instance, attribute_validator.attribute)
29
- return true
30
- end
27
+ next unless attribute_validator
28
+
29
+ return process_validator(instance, attribute_validator)
31
30
  end
32
31
 
33
32
  false
34
33
  end
34
+
35
+ def process_validator(instance, attribute_validator)
36
+ return false unless attribute_validator.validator.perform_db_validation?
37
+
38
+ attribute_validator.validator.apply_error(instance, attribute_validator.attribute)
39
+ true
40
+ end
35
41
  end
36
42
  end
@@ -22,7 +22,7 @@ module DatabaseValidations
22
22
  else
23
23
  validator.attributes.map do |attribute|
24
24
  columns = KeyGenerator.unify_columns(attribute, validator.options[:scope])
25
- index = adapter.find_index(columns, validator.where)
25
+ index = adapter.find_unique_index(columns, validator.where)
26
26
  [KeyGenerator.for_unique_index(index.name), attribute]
27
27
  end.to_h
28
28
  end
@@ -14,16 +14,26 @@ module DatabaseValidations
14
14
  end
15
15
 
16
16
  def create_or_update(*args, &block)
17
+ options = args.extract_options!
18
+
19
+ if options[:validate] == false
20
+ super
21
+ else
22
+ rescue_from_database_exceptions { super }
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def rescue_from_database_exceptions(&block)
17
29
  self._database_validations_fallback = false
18
- ActiveRecord::Base.connection.transaction(requires_new: true) { super }
30
+ self.class.connection.transaction(requires_new: true, &block)
19
31
  rescue ActiveRecord::InvalidForeignKey, ActiveRecord::RecordNotUnique => e
20
32
  raise e unless Rescuer.handled?(self, e)
21
33
 
22
34
  raise ActiveRecord::RecordInvalid, self
23
35
  end
24
36
 
25
- private
26
-
27
37
  def perform_validations(options = {})
28
38
  options[:validate] == false || valid_without_database_validations?(options[:context])
29
39
  end
@@ -21,6 +21,10 @@ module DatabaseValidations
21
21
  Checkers::DbPresenceValidator.validate!(self)
22
22
  end
23
23
 
24
+ def perform_db_validation?
25
+ true
26
+ end
27
+
24
28
  # TODO: add support of optional db_belongs_to
25
29
  def validate(record)
26
30
  if record._database_validations_fallback
@@ -55,7 +59,7 @@ module DatabaseValidations
55
59
  options[:optional] = true
56
60
  end
57
61
 
58
- belongs_to(name, scope, options)
62
+ belongs_to(name, scope, **options)
59
63
 
60
64
  validates_with DatabaseValidations::DbPresenceValidator, _merge_attributes([name, message: DatabaseValidations::DbPresenceValidator::REFLECTION_MESSAGE]) # rubocop:disable Metrics/LineLength
61
65
  end
@@ -1,5 +1,7 @@
1
1
  module DatabaseValidations
2
2
  class DbUniquenessValidator < ActiveRecord::Validations::UniquenessValidator
3
+ DEFAULT_MODE = :optimized
4
+
3
5
  attr_reader :index_name, :where, :klass
4
6
 
5
7
  # Used to make 3rd party libraries work correctly
@@ -19,8 +21,7 @@ module DatabaseValidations
19
21
  options[:conditions] = -> { where(condition) }
20
22
  end
21
23
 
22
- @index_name = options.delete(:index_name) if options.key?(:index_name)
23
- @where = options.delete(:where) if options.key?(:where)
24
+ handle_custom_options(options)
24
25
 
25
26
  super
26
27
 
@@ -28,8 +29,12 @@ module DatabaseValidations
28
29
  Checkers::DbUniquenessValidator.validate!(self)
29
30
  end
30
31
 
32
+ def perform_db_validation?
33
+ @mode != :standard
34
+ end
35
+
31
36
  def validate(record)
32
- super if record._database_validations_fallback
37
+ super if perform_query? || record._database_validations_fallback
33
38
  end
34
39
 
35
40
  def apply_error(instance, attribute)
@@ -38,6 +43,18 @@ module DatabaseValidations
38
43
 
39
44
  instance.errors.add(attribute, :taken, error_options)
40
45
  end
46
+
47
+ private
48
+
49
+ def handle_custom_options(options)
50
+ @index_name = options.delete(:index_name) if options.key?(:index_name)
51
+ @where = options.delete(:where) if options.key?(:where)
52
+ @mode = (options.delete(:mode).presence || DEFAULT_MODE).to_sym
53
+ end
54
+
55
+ def perform_query?
56
+ @mode != :optimized
57
+ end
41
58
  end
42
59
 
43
60
  module ClassMethods
@@ -41,6 +41,10 @@ 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
 
@@ -79,6 +83,7 @@ RSpec::Matchers.define :validate_db_uniqueness_of do |field| # rubocop:disable M
79
83
  desc += "where: '#{@where}'; " if @where
80
84
  desc += "index_name: '#{@index_name}'; " if @index_name
81
85
  desc += 'be case insensitive.' unless @case_sensitive
86
+ desc += 'be case sensitive.' if @case_sensitive
82
87
  desc
83
88
  end
84
89
 
@@ -1,3 +1,3 @@
1
1
  module DatabaseValidations
2
- VERSION = '0.9.0'.freeze
2
+ VERSION = '1.0.0'.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.9.0
4
+ version: 1.0.0
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-08-18 00:00:00.000000000 Z
11
+ date: 2021-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -100,14 +100,14 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: '12.3'
103
+ version: '13.0'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: '12.3'
110
+ version: '13.0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: rspec
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -208,7 +208,7 @@ homepage: https://github.com/toptal/database_validations
208
208
  licenses:
209
209
  - MIT
210
210
  metadata: {}
211
- post_install_message:
211
+ post_install_message:
212
212
  rdoc_options: []
213
213
  require_paths:
214
214
  - lib
@@ -223,9 +223,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
223
  - !ruby/object:Gem::Version
224
224
  version: '0'
225
225
  requirements: []
226
- rubyforge_project:
227
- rubygems_version: 2.7.9
228
- signing_key:
226
+ rubygems_version: 3.1.2
227
+ signing_key:
229
228
  specification_version: 4
230
229
  summary: Provide compatibility between database constraints and ActiveRecord validations
231
230
  with better performance and consistency.