database_validations 0.3.6 → 0.3.7

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7f37a8d6c8fcdc4819f71e00044bdffddffafe2f485778bb53c3fb81293bd762
4
- data.tar.gz: d0e1fb1c893b7127d184e55dbcf963a52e98adb49988fd67ccca13757e479714
3
+ metadata.gz: aefb40cecf33b68606f21a491cd41ffa3e1bc4ffcf891b837b9b8693e04f2239
4
+ data.tar.gz: ec3c2ab6955ec4f5da93804bcb6f2382d577331553b9d710af75a62cf9e8ccea
5
5
  SHA512:
6
- metadata.gz: 14b3e4c95b471218acd03de35ab297709ae539b285d24fa00c200c922e969d3fadc06b1135156790f34397ef9c9fe3ade5f17d467981a8fbd5fa2e373b2c7876
7
- data.tar.gz: 3b2320c372c8b376e3c8a0770a390c885844f58834b1bdfded46632df772365ac09c348ac104900a2bfe1e6d9b73f67a4b12305e2bf7494f21b363059e5464b2
6
+ metadata.gz: 1c341fa892d214a3fce105e683c90b5633c7c8ccc5917c675762e710cde2443e332bf66768831b772436c6eb6bfd823136411f409e0c16ef58cb85347f8b87c8
7
+ data.tar.gz: c6a838adc69c1558acede0e38df24d30bac20d7c348951c8059966daf59e649ae7887879810e68f88df88d69b277451a7fe476bca8a237842c970103184c073b
@@ -3,6 +3,8 @@ require 'active_record'
3
3
  require 'database_validations/version'
4
4
  require 'database_validations/railtie' if defined?(Rails)
5
5
  require 'database_validations/uniqueness_validator'
6
+ require 'database_validations/uniqueness_options'
7
+ require 'database_validations/uniqueness_options_storage'
6
8
  require 'database_validations/errors'
7
9
  require 'database_validations/helpers'
8
10
  require 'database_validations/adapters'
@@ -9,9 +9,9 @@ module DatabaseValidations
9
9
 
10
10
  def factory(model)
11
11
  case (database = model.connection.adapter_name.downcase.to_sym)
12
- when :sqlite then Adapters::SqliteAdapter.new(model)
13
- when :postgresql then Adapters::PostgresqlAdapter.new(model)
14
- when :mysql2 then Adapters::MysqlAdapter.new(model)
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
15
  else
16
16
  raise Errors::UnknownDatabase.new(database)
17
17
  end
@@ -1,18 +1,44 @@
1
1
  module DatabaseValidations
2
2
  module Adapters
3
3
  class BaseAdapter
4
- attr_reader :model
4
+ SUPPORTED_OPTIONS = [].freeze
5
+ ADAPTER = :base
5
6
 
6
7
  def initialize(model)
7
8
  @model = model
8
9
  end
9
10
 
10
- def index_columns(index_name)
11
- model.connection
12
- .indexes(model.table_name)
13
- .find { |index| index.name == index_name }
14
- .columns
11
+ def find_index_by_name(index_name)
12
+ indexes.find { |index| index.name == index_name }
13
+ end
14
+
15
+ def find_index_by_columns(index_columns)
16
+ indexes.find { |index| index.columns.map(&:to_s).sort == index_columns }
17
+ end
18
+
19
+ def indexes
20
+ model.connection.indexes(model.table_name).select(&:unique)
21
+ end
22
+
23
+ # @return [Symbol]
24
+ def adapter_name
25
+ self.class::ADAPTER
15
26
  end
27
+
28
+ # @param [Symbol] option_name
29
+ # @return [Boolean]
30
+ def support_option?(option_name)
31
+ supported_options.include?(option_name.to_sym)
32
+ end
33
+
34
+ # @return [Array<Symbol>]
35
+ def supported_options
36
+ self.class::SUPPORTED_OPTIONS
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :model
16
42
  end
17
43
  end
18
44
  end
@@ -1,9 +1,12 @@
1
1
  module DatabaseValidations
2
2
  module Adapters
3
3
  class MysqlAdapter < BaseAdapter
4
+ SUPPORTED_OPTIONS = %i[scope message].freeze
5
+ ADAPTER = :mysql2
6
+
4
7
  def error_columns(error_message)
5
8
  index_name = error_message[/key '([^']+)'/, 1]
6
- index_columns(index_name)
9
+ find_index_by_name(index_name).columns
7
10
  end
8
11
  end
9
12
  end
@@ -1,9 +1,12 @@
1
1
  module DatabaseValidations
2
2
  module Adapters
3
3
  class PostgresqlAdapter < BaseAdapter
4
+ SUPPORTED_OPTIONS = %i[scope message].freeze
5
+ ADAPTER = :postgresql
6
+
4
7
  def error_columns(error_message)
5
8
  index_name = error_message[/unique constraint "([^"]+)"/, 1]
6
- index_columns(index_name)
9
+ find_index_by_name(index_name).columns
7
10
  end
8
11
  end
9
12
  end
@@ -1,6 +1,9 @@
1
1
  module DatabaseValidations
2
2
  module Adapters
3
3
  class SqliteAdapter < BaseAdapter
4
+ SUPPORTED_OPTIONS = %i[scope message].freeze
5
+ ADAPTER = :sqlite
6
+
4
7
  def error_columns(error_message)
5
8
  error_message.scan(/#{model.table_name}\.([^,:]+)/).flatten
6
9
  end
@@ -21,5 +21,16 @@ module DatabaseValidations
21
21
  super "Unknown database: #{self.database}"
22
22
  end
23
23
  end
24
+
25
+ class OptionIsNotSupported < Base
26
+ attr_reader :option, :database, :supported_options
27
+
28
+ def initialize(option, database, supported_options)
29
+ @option = option
30
+ @database = database
31
+ @supported_options = supported_options
32
+ super "Option #{self.option} is not supported for #{self.database}. Supported options are: #{self.supported_options}"
33
+ end
34
+ end
24
35
  end
25
36
  end
@@ -2,39 +2,37 @@ module DatabaseValidations
2
2
  module Helpers
3
3
  module_function
4
4
 
5
- def raise_if_index_missed!(model, columns)
6
- index = model.connection
7
- .indexes(model.table_name)
8
- .select(&:unique)
9
- .find { |index| index.columns.map(&:to_s).sort == columns }
5
+ def handle_unique_error!(instance, error)
6
+ key = generate_key(Adapters.factory(instance.class).error_columns(error.message))
10
7
 
11
- raise Errors::IndexNotFound.new(columns) unless index
12
- end
13
-
14
- def handle_unique_error(instance, error)
15
- columns = DatabaseValidations::Adapters
16
- .factory(instance.class)
17
- .error_columns(error.message)
18
- .map!(&:to_s)
19
- .sort!
20
-
21
- options = uniqueness_validators_options(instance.class)[columns]
22
-
23
- error_options = options.except(:case_sensitive, :scope, :conditions, :attributes)
24
- error_options[:value] = instance.public_send(options[:attributes])
8
+ each_options_storage(instance.class) do |storage|
9
+ # storage has this then return
10
+ return storage[key].handle_unique_error(instance) if storage[key]
11
+ end
25
12
 
26
- instance.errors.add(options[:attributes], :taken, error_options)
13
+ raise error
27
14
  end
28
15
 
29
- def uniqueness_validators_options(klass)
30
- validators_options = klass.instance_variable_get(:'@validates_db_uniqueness_opts') || {}
31
-
32
- while klass.superclass.respond_to?(:validates_db_uniqueness_of)
33
- validators_options.reverse_merge!(klass.superclass.instance_variable_get(:'@validates_db_uniqueness_opts') || {})
16
+ def each_options_storage(klass)
17
+ while klass.respond_to?(:validates_db_uniqueness_of)
18
+ storage = klass.instance_variable_get(:'@validates_db_uniqueness_opts')
19
+ yield(storage) if storage
34
20
  klass = klass.superclass
35
21
  end
22
+ end
23
+
24
+ def each_validator(klass)
25
+ each_options_storage(klass) do |storage|
26
+ storage.each_validator { |validator| yield(validator) }
27
+ end
28
+ end
29
+
30
+ def unify_columns(*columns)
31
+ columns.flatten.map(&:to_s).sort
32
+ end
36
33
 
37
- validators_options
34
+ def generate_key(*columns)
35
+ unify_columns(columns).join('__')
38
36
  end
39
37
  end
40
38
  end
@@ -0,0 +1,48 @@
1
+ module DatabaseValidations
2
+ class UniquenessOptions
3
+ def initialize(field, options, adapter)
4
+ @field = field
5
+ @options = options
6
+ @adapter = adapter
7
+
8
+ raise_if_unsupported_options!
9
+ raise_if_index_missed! unless ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
10
+ end
11
+
12
+ def handle_unique_error(instance)
13
+ error_options = options.except(:case_sensitive, :scope, :conditions, :attributes)
14
+ error_options[:value] = instance.public_send(options[:attributes])
15
+
16
+ instance.errors.add(options[:attributes], :taken, error_options)
17
+ end
18
+
19
+ def validates_uniqueness_options
20
+ options.merge(allow_nil: true, case_sensitive: true, allow_blank: false)
21
+ end
22
+
23
+ def key
24
+ @key ||= Helpers.generate_key(columns)
25
+ end
26
+
27
+ def columns
28
+ @columns ||= Helpers.unify_columns(field, Array.wrap(options[:scope]))
29
+ end
30
+
31
+ def raise_if_unsupported_options!
32
+ options.except(:attributes).each_key do |option|
33
+ unless adapter.support_option?(option)
34
+ raise Errors::OptionIsNotSupported.new(option, adapter.adapter_name, adapter.supported_options)
35
+ end
36
+ end
37
+ end
38
+
39
+ def raise_if_index_missed!
40
+ raise Errors::IndexNotFound.new(columns) unless adapter.find_index_by_columns(columns)
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :adapter, :field, :options
46
+ end
47
+ end
48
+
@@ -0,0 +1,26 @@
1
+ module DatabaseValidations
2
+ class UniquenessOptionsStorage
3
+
4
+ def initialize(klass)
5
+ @adapter = Adapters.factory(klass)
6
+ @storage = {}
7
+ end
8
+
9
+ def push(field, options)
10
+ uniqueness_options = UniquenessOptions.new(field, options, adapter)
11
+ storage[uniqueness_options.key] = uniqueness_options
12
+ end
13
+
14
+ def [](key)
15
+ storage[key]
16
+ end
17
+
18
+ def each_validator
19
+ storage.each_value { |validator| yield(validator) }
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :storage, :adapter
25
+ end
26
+ end
@@ -1,5 +1,5 @@
1
1
  module DatabaseValidations
2
- module DatabaseUniquenessValidator
2
+ module UniquenessHandlers
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
@@ -8,9 +8,8 @@ module DatabaseValidations
8
8
  def valid?(context = nil)
9
9
  output = super(context)
10
10
 
11
-
12
- DatabaseValidations::Helpers.uniqueness_validators_options(self.class).each_value do |opts|
13
- validates_with(ActiveRecord::Validations::UniquenessValidator, opts.merge(allow_nil: true))
11
+ Helpers.each_validator(self.class) do |validator|
12
+ validates_with(ActiveRecord::Validations::UniquenessValidator, validator.validates_uniqueness_options)
14
13
  end
15
14
 
16
15
  errors.empty? && output
@@ -22,14 +21,14 @@ module DatabaseValidations
22
21
  def save(options = {})
23
22
  ActiveRecord::Base.connection.transaction(requires_new: true) { super }
24
23
  rescue ActiveRecord::RecordNotUnique => e
25
- DatabaseValidations::Helpers.handle_unique_error(self, e)
24
+ Helpers.handle_unique_error!(self, e)
26
25
  false
27
26
  end
28
27
 
29
28
  def save!(options = {})
30
29
  ActiveRecord::Base.connection.transaction(requires_new: true) { super }
31
30
  rescue ActiveRecord::RecordNotUnique => e
32
- DatabaseValidations::Helpers.handle_unique_error(self, e)
31
+ Helpers.handle_unique_error!(self, e)
33
32
  raise ActiveRecord::RecordInvalid, self
34
33
  end
35
34
 
@@ -42,19 +41,15 @@ module DatabaseValidations
42
41
 
43
42
  module ClassMethods
44
43
  def validates_db_uniqueness_of(*attributes)
45
- @validates_db_uniqueness_opts ||= {}
44
+ @validates_db_uniqueness_opts ||= DatabaseValidations::UniquenessOptionsStorage.new(self)
46
45
 
47
46
  options = attributes.extract_options!
48
47
 
49
48
  attributes.each do |attribute|
50
- columns = [attribute, Array.wrap(options[:scope])].flatten!.map!(&:to_s).sort!
51
-
52
- DatabaseValidations::Helpers.raise_if_index_missed!(self, columns) unless ENV['SKIP_DB_UNIQUENESS_VALIDATOR_INDEX_CHECK']
53
-
54
- @validates_db_uniqueness_opts[columns] = options.merge(attributes: attribute)
49
+ @validates_db_uniqueness_opts.push(attribute, options.merge(attributes: attribute))
55
50
  end
56
51
 
57
- include(DatabaseUniquenessValidator)
52
+ include(DatabaseValidations::UniquenessHandlers)
58
53
  end
59
54
  end
60
55
  end
@@ -1,3 +1,3 @@
1
1
  module DatabaseValidations
2
- VERSION = "0.3.6"
2
+ VERSION = "0.3.7"
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.3.6
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgeniy Demin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-11 00:00:00.000000000 Z
11
+ date: 2018-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -152,6 +152,8 @@ files:
152
152
  - lib/database_validations/errors.rb
153
153
  - lib/database_validations/helpers.rb
154
154
  - lib/database_validations/railtie.rb
155
+ - lib/database_validations/uniqueness_options.rb
156
+ - lib/database_validations/uniqueness_options_storage.rb
155
157
  - lib/database_validations/uniqueness_validator.rb
156
158
  - lib/database_validations/version.rb
157
159
  - lib/tasks/database_validations.rake