database_validations 0.3.6 → 0.3.7

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