database_validations 0.3.0 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75cc8e49376be81dd56be2da6de60dd9e4763243af4aef1ecb3bc5d5324bfabc
4
- data.tar.gz: 7d091827be83bcfa46e89bdbaa7ff5a1964c063272a3b3099b1169e678d5d1b0
3
+ metadata.gz: a7c3f11bd0b6a20ebd9cbcbe9134ae84e049c53e13d0a67faaa3da570994adc8
4
+ data.tar.gz: 1a45078331bccc6a9fdd6e59426b766dce95375ec9ffa7ac3130bb91df86f761
5
5
  SHA512:
6
- metadata.gz: c60c9a3eb7ea1783235b4636d7c9baa0af690a2218593ba96853e6f18647a01feb0449676c920d9afb6e73145d3d44df0ce1f02f153c4a161a22983204823102
7
- data.tar.gz: 478bcddd2b83c208b34632bf782ba27e9c948c878fb7c6d751f65fb08c6ac206f1d3a7c6b28706299499f592e827dd6b8381a306014e03dd8200776ccb24ec60
6
+ metadata.gz: 75563aa5561d03288ae9dbe9b38e5fc8d87955f9227150d0c3d782eb42bf02e12d259d2614c4e35d2dd344958bb306ba87a66f46747b04608b5082c5f430210e
7
+ data.tar.gz: 00f656712648b20f62d90d72db0b233e8f69d6a552e9adc7eb42c45cd85d2f17622812776223990dae9aea21ed42d586719cdff4a030eff25218cfde513c8b7e
@@ -0,0 +1,20 @@
1
+ require 'database_validations/adapters/base_adapter'
2
+ require 'database_validations/adapters/sqlite_adapter'
3
+ require 'database_validations/adapters/postgresql_adapter'
4
+ require 'database_validations/adapters/mysql_adapter'
5
+
6
+ module DatabaseValidations
7
+ module Adapters
8
+ module_function
9
+
10
+ def factory(model)
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)
15
+ else
16
+ raise Errors::UnknownDatabase.new(database)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module DatabaseValidations
2
+ module Adapters
3
+ class BaseAdapter
4
+ attr_reader :model
5
+
6
+ def initialize(model)
7
+ @model = model
8
+ end
9
+
10
+ def index_columns(index_name)
11
+ model.connection
12
+ .indexes(model.table_name)
13
+ .find { |index| index.name == index_name }
14
+ .columns
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,10 @@
1
+ module DatabaseValidations
2
+ module Adapters
3
+ class MysqlAdapter < BaseAdapter
4
+ def error_columns(error_message)
5
+ index_name = error_message[/key '([^']+)'/, 1]
6
+ index_columns(index_name)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module DatabaseValidations
2
+ module Adapters
3
+ class PostgresqlAdapter < BaseAdapter
4
+ def error_columns(error_message)
5
+ index_name = error_message[/unique constraint "([^"]+)"/, 1]
6
+ index_columns(index_name)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module DatabaseValidations
2
+ module Adapters
3
+ class SqliteAdapter < BaseAdapter
4
+ def error_columns(error_message)
5
+ error_message.scan(/#{model.table_name}\.([^,:]+)/).flatten
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module DatabaseValidations
2
+ module Errors
3
+ class Base < StandardError; end
4
+
5
+ class IndexNotFound < Base
6
+ attr_reader :columns
7
+
8
+ def initialize(columns)
9
+ @columns = columns.map(&:to_s)
10
+ super "No unique index found with columns: #{self.columns}"
11
+ end
12
+ end
13
+
14
+ class UnknownDatabase < Base
15
+ attr_reader :database
16
+
17
+ def initialize(database)
18
+ @database = database
19
+ super "Unknown database: #{self.database}"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,29 @@
1
+ module DatabaseValidations
2
+ module Helpers
3
+ module_function
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 }
10
+
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 = instance.class.validates_db_uniqueness[columns]
22
+
23
+ error_options = options.except(:case_sensitive, :scope, :conditions, :attributes)
24
+ error_options[:value] = instance.public_send(options[:attributes])
25
+
26
+ instance.errors.add(options[:attributes], :taken, error_options)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,64 @@
1
+ module DatabaseValidations
2
+ module DatabaseUniquenessValidator
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ alias_method :valid_without_uniqueness?, :valid?
7
+
8
+ def valid?(context = nil)
9
+ output = super(context)
10
+
11
+ self.class.validates_db_uniqueness.each_value do |opts|
12
+ validates_with(ActiveRecord::Validations::UniquenessValidator, opts.merge(allow_nil: true))
13
+ end
14
+
15
+ errors.empty? && output
16
+ end
17
+
18
+ alias_method :validate, :valid?
19
+ end
20
+
21
+ def save(options = {})
22
+ super
23
+ rescue ActiveRecord::RecordNotUnique => e
24
+ DatabaseValidations::Helpers.handle_unique_error(self, e)
25
+ false
26
+ end
27
+
28
+ def save!(options = {})
29
+ super
30
+ rescue ActiveRecord::RecordNotUnique => e
31
+ DatabaseValidations::Helpers.handle_unique_error(self, e)
32
+ raise ActiveRecord::RecordInvalid, self
33
+ end
34
+
35
+ private
36
+
37
+ def perform_validations(options = {})
38
+ options[:validate] == false || valid_without_uniqueness?(options[:context])
39
+ end
40
+ end
41
+
42
+ module ClassMethods
43
+ def validates_db_uniqueness_of(*attributes)
44
+ @validates_db_uniqueness ||= {}
45
+
46
+ options = attributes.extract_options!
47
+
48
+ attributes.each do |attribute|
49
+ columns = [attribute, Array.wrap(options[:scope])].flatten!.map!(&:to_s).sort!
50
+
51
+ DatabaseValidations::Helpers.raise_if_index_missed!(self, columns)
52
+
53
+ @validates_db_uniqueness[columns] = options.merge(attributes: attribute)
54
+ end
55
+
56
+ include(DatabaseUniquenessValidator)
57
+ end
58
+
59
+ def validates_db_uniqueness
60
+ derived = superclass.respond_to?(:validates_db_uniqueness) ? superclass.validates_db_uniqueness : {}
61
+ derived.merge(@validates_db_uniqueness || {})
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,3 @@
1
+ module DatabaseValidations
2
+ VERSION = "0.3.1"
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.0
4
+ version: 0.3.1
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-08-31 00:00:00.000000000 Z
11
+ date: 2018-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -144,6 +144,15 @@ files:
144
144
  - LICENSE.txt
145
145
  - README.md
146
146
  - lib/database_validations.rb
147
+ - lib/database_validations/adapters.rb
148
+ - lib/database_validations/adapters/base_adapter.rb
149
+ - lib/database_validations/adapters/mysql_adapter.rb
150
+ - lib/database_validations/adapters/postgresql_adapter.rb
151
+ - lib/database_validations/adapters/sqlite_adapter.rb
152
+ - lib/database_validations/errors.rb
153
+ - lib/database_validations/helpers.rb
154
+ - lib/database_validations/uniqueness_validator.rb
155
+ - lib/database_validations/version.rb
147
156
  homepage: https://github.com/toptal/database_validations
148
157
  licenses:
149
158
  - MIT