database_validations 0.3.0 → 0.3.1

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