validates_by_schema 0.2.2 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +45 -14
- data/Rakefile +27 -11
- data/lib/validates_by_schema/validation_option.rb +76 -14
- data/lib/validates_by_schema/version.rb +1 -1
- data/lib/validates_by_schema.rb +45 -13
- data/spec/ci/rails50.gemfile +10 -0
- data/spec/ci/rails50.gemfile.lock +118 -0
- data/spec/ci/rails52.gemfile +8 -0
- data/spec/ci/rails60.gemfile +8 -0
- data/spec/config/database.yml +31 -0
- data/spec/config/schema.rb +44 -0
- data/spec/db/test.sqlite3 +0 -0
- data/spec/spec_helper.rb +75 -0
- data/spec/support/models/contraption.rb +7 -0
- data/spec/support/models/gadget.rb +5 -0
- data/spec/support/models/gizmo.rb +7 -0
- data/spec/support/models/sub_contraption.rb +4 -0
- data/spec/support/models/widget.rb +6 -0
- data/spec/validations_spec.rb +340 -0
- metadata +84 -53
- data/lib/tasks/validates_by_schema_tasks.rake +0 -4
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a44382b4c751d51c6056b9eae1daed6f8eedfe8040383fe38ce717beafc2c84d
|
4
|
+
data.tar.gz: 553ffe11b1a3c83f6c2f1126954d691553465492f7f3820f6530b14a48675971
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b404ea525a570681cc76d936755f6f6fbc89a310e8409d0480161a2a9aad11e1c803445c30b0cf03ce951d02ae63f614715e48c3a9bfde80455e5cec4b293c71
|
7
|
+
data.tar.gz: a7b24d0fe512674063b0c055f155b5cd608661fcf284634399354c6d63a3acfbf7fba6e9de335ea5e55e25765cedc8bda60505879aa7d9a09f96a3e828ebadba
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# Validates By Schema (validates_by_schema)
|
2
|
-
|
3
|
-
[![
|
4
|
-
[![
|
5
|
-
[![
|
2
|
+
|
3
|
+
[![Gem Version](http://img.shields.io/gem/v/validates_by_schema.svg?style=flat)](https://rubygems.org/gems/validates_by_schema)
|
4
|
+
[![Build Status](http://img.shields.io/github/workflow/status/joshwlewis/validates_by_schema/Build?style=flat)](https://github.com/joshwlewis/validates_by_schema/actions/workflows/build.yml)
|
5
|
+
[![Coverage Status](http://img.shields.io/coveralls/joshwlewis/validates_by_schema.svg?style=flat)](https://coveralls.io/r/joshwlewis/validates_by_schema)
|
6
|
+
[![Code Climate](http://img.shields.io/codeclimate/maintainability/joshwlewis/validates_by_schema.svg?style=flat)](https://codeclimate.com/github/joshwlewis/validates_by_schema)
|
6
7
|
|
7
8
|
Automatic validation based on your database schema column types and limits. Keep your code DRY by inferring column validations from table properties!
|
8
9
|
|
@@ -11,21 +12,35 @@ Automatic validation based on your database schema column types and limits. Keep
|
|
11
12
|
Say you had a table setup like this:
|
12
13
|
|
13
14
|
```ruby
|
14
|
-
create_table "widgets", :
|
15
|
-
t.integer
|
16
|
-
t.decimal
|
17
|
-
t.string
|
15
|
+
create_table "widgets", force: true do |t|
|
16
|
+
t.integer "quantity", limit: 2, null: false
|
17
|
+
t.decimal "thickness", precision: 4, scale: 4
|
18
|
+
t.string "color", null: false
|
19
|
+
t.boolean "flagged", null: false, default: false
|
20
|
+
t.integer "other_id", null: false
|
21
|
+
t.index ["other_id", "color"], unique: true
|
18
22
|
end
|
19
23
|
```
|
20
24
|
|
21
25
|
Then these validations are inferred when you add `validates_by_schema` to your model:
|
22
26
|
|
23
27
|
```ruby
|
24
|
-
validates :quantity,
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
28
|
+
validates :quantity, presence: true,
|
29
|
+
numericality: { allow_nil: true,
|
30
|
+
only_integer: true,
|
31
|
+
greater_than: -32768,
|
32
|
+
less_than: 32768}
|
33
|
+
validates :thickness, numericality: { allow_nil: true,
|
34
|
+
less_than_or_equal_to: 0.999,
|
35
|
+
greater_than_or_equal_to: -0.999 }
|
36
|
+
validates :color, presence: true, length: { allow_nil: true, maximum: 255 }
|
37
|
+
validates :flagged, inclusion: { in: [true, false], allow_nil: false }
|
38
|
+
validates :other, presence: true
|
39
|
+
validates :other_id,
|
40
|
+
uniqueness: {
|
41
|
+
scope: :color,
|
42
|
+
if: -> { |model| model.other_id_changed? || model.color_changed? }
|
43
|
+
}
|
29
44
|
```
|
30
45
|
|
31
46
|
## Installation
|
@@ -58,8 +73,24 @@ validates_by_schema only: [:body, :description]
|
|
58
73
|
validates_by_schema except: [:name, :title]
|
59
74
|
```
|
60
75
|
|
76
|
+
The primary key and timestamp columns are not validated.
|
77
|
+
|
78
|
+
If you want to opt out of automatic uniqueness validations globally, add the following line to an initializer:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
ValidatesBySchema.validate_uniqueness = false
|
82
|
+
```
|
83
|
+
|
61
84
|
## Notes
|
62
85
|
|
63
86
|
Column properties are inferred by your database adapter (like pg, mysql2, sqlite3), and does not depend on migration files or schema.rb. As such, you could use this on projects where the database where Rails is not in control of the database configuration.
|
64
87
|
|
65
|
-
This has been tested with mysql, postgresql, and sqlite3. It should work with any other database that has reliable adapter.
|
88
|
+
This has been tested with mysql, postgresql, and sqlite3. It should work with any other database that has reliable adapter.
|
89
|
+
|
90
|
+
## Contributing
|
91
|
+
|
92
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/joshwlewis/validates_by_schema.
|
93
|
+
|
94
|
+
## License
|
95
|
+
|
96
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -20,19 +20,35 @@ RDoc::Task.new(:rdoc) do |rdoc|
|
|
20
20
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
23
|
Bundler::GemHelper.install_tasks
|
27
24
|
|
28
|
-
require '
|
25
|
+
require 'rspec/core/rake_task'
|
29
26
|
|
30
|
-
|
31
|
-
t.
|
32
|
-
t.
|
33
|
-
t.pattern = 'test/**/*_test.rb'
|
34
|
-
t.verbose = false
|
27
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
28
|
+
t.pattern = Dir.glob('spec/**/*_spec.rb')
|
29
|
+
t.rspec_opts = ['--backtrace']
|
35
30
|
end
|
36
31
|
|
37
|
-
|
38
|
-
|
32
|
+
task default: :spec
|
33
|
+
|
34
|
+
namespace :db do
|
35
|
+
task :drop do
|
36
|
+
puts 'dropping'
|
37
|
+
case ENV['DB']
|
38
|
+
when 'postgresql'
|
39
|
+
exec "psql -c 'drop database if exists validates_by_schema_test;' -U postgres -h localhost"
|
40
|
+
when 'mysql'
|
41
|
+
exec "mysql -e 'drop database if exists validates_by_schema_test;' -u root -h 127.0.0.1"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
task :create do
|
46
|
+
puts 'creating'
|
47
|
+
case ENV['DB']
|
48
|
+
when 'postgresql'
|
49
|
+
exec "psql -c 'create database validates_by_schema_test;' -U postgres -h localhost"
|
50
|
+
when 'mysql'
|
51
|
+
exec "mysql -e 'create database validates_by_schema_test;' -u root -h 127.0.0.1"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -1,12 +1,40 @@
|
|
1
1
|
class ValidatesBySchema::ValidationOption
|
2
2
|
# column here must be an ActiveRecord column
|
3
3
|
# i.e. MyARModel.columns.first
|
4
|
-
attr_accessor :column
|
4
|
+
attr_accessor :klass, :column
|
5
5
|
|
6
|
-
def initialize(column)
|
6
|
+
def initialize(klass, column)
|
7
|
+
@klass = klass
|
7
8
|
@column = column
|
8
9
|
end
|
9
10
|
|
11
|
+
def define!
|
12
|
+
if association
|
13
|
+
# Only presence and uniqueness are handled for associations.
|
14
|
+
# presence on the association name, uniqueness on the column name.
|
15
|
+
define_belongs_to_presence_validation
|
16
|
+
else
|
17
|
+
define_validations(to_hash)
|
18
|
+
end
|
19
|
+
define_uniqueness_validations if ValidatesBySchema.validate_uniqueness
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def define_belongs_to_presence_validation
|
25
|
+
klass.validates association.name, presence: true if !ActiveRecord::Base.belongs_to_required_by_default && presence
|
26
|
+
end
|
27
|
+
|
28
|
+
def define_uniqueness_validations
|
29
|
+
uniqueness.each do |options|
|
30
|
+
define_validations(uniqueness: options)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def define_validations(options)
|
35
|
+
klass.validates column.name, options if options.present?
|
36
|
+
end
|
37
|
+
|
10
38
|
def presence?
|
11
39
|
presence && column.type != :boolean
|
12
40
|
end
|
@@ -15,8 +43,12 @@ class ValidatesBySchema::ValidationOption
|
|
15
43
|
!column.null
|
16
44
|
end
|
17
45
|
|
46
|
+
def enum?
|
47
|
+
klass.respond_to?(:defined_enums) && klass.defined_enums.has_key?(column.name)
|
48
|
+
end
|
49
|
+
|
18
50
|
def numericality?
|
19
|
-
[:integer, :decimal, :float].include?
|
51
|
+
[:integer, :decimal, :float].include?(column.type) && !enum?
|
20
52
|
end
|
21
53
|
|
22
54
|
def numericality
|
@@ -27,20 +59,48 @@ class ValidatesBySchema::ValidationOption
|
|
27
59
|
numericality[:less_than] = integer_max
|
28
60
|
numericality[:greater_than] = -integer_max
|
29
61
|
end
|
30
|
-
elsif column.type == :decimal
|
62
|
+
elsif column.type == :decimal && decimal_max
|
31
63
|
numericality[:less_than_or_equal_to] = decimal_max
|
32
64
|
numericality[:greater_than_or_equal_to] = -decimal_max
|
33
65
|
end
|
34
|
-
numericality[:allow_nil] =
|
66
|
+
numericality[:allow_nil] = true
|
35
67
|
numericality
|
36
68
|
end
|
37
69
|
|
70
|
+
def uniqueness
|
71
|
+
unique_indexes.map do |index|
|
72
|
+
{
|
73
|
+
scope: index.columns.reject { |col| col == column.name },
|
74
|
+
conditions: -> { where(index.where) },
|
75
|
+
allow_nil: column.null,
|
76
|
+
case_sensitive: case_sensitive?,
|
77
|
+
if: ->(model) { index.columns.any? { |c| model.send("#{c}_changed?") } }
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def unique_indexes
|
83
|
+
klass
|
84
|
+
.connection
|
85
|
+
.indexes(klass.table_name)
|
86
|
+
.select { |index| index.unique && index.columns.first == column.name }
|
87
|
+
end
|
88
|
+
|
89
|
+
def case_sensitive?
|
90
|
+
!klass.connection.respond_to?(:collation) ||
|
91
|
+
!klass.connection.collation.end_with?('_ci')
|
92
|
+
end
|
93
|
+
|
94
|
+
def array?
|
95
|
+
column.respond_to?(:array) && column.array
|
96
|
+
end
|
97
|
+
|
38
98
|
def length?
|
39
|
-
[:string, :text].include?(column.type) && column.limit
|
99
|
+
[:string, :text].include?(column.type) && column.limit && !array?
|
40
100
|
end
|
41
101
|
|
42
102
|
def length
|
43
|
-
{:
|
103
|
+
{ maximum: column.limit, allow_nil: true }
|
44
104
|
end
|
45
105
|
|
46
106
|
def inclusion?
|
@@ -48,23 +108,25 @@ class ValidatesBySchema::ValidationOption
|
|
48
108
|
end
|
49
109
|
|
50
110
|
def inclusion
|
51
|
-
{:
|
111
|
+
{ in: [true, false], allow_nil: column.null }
|
52
112
|
end
|
53
113
|
|
54
114
|
def integer_max
|
55
|
-
(2
|
115
|
+
(2**(8 * column.limit)) / 2 if column.limit
|
56
116
|
end
|
57
117
|
|
58
118
|
def decimal_max
|
59
|
-
10.0**(column.precision-column.scale) - 10.0**(-column.scale)
|
119
|
+
10.0**(column.precision - column.scale) - 10.0**(-column.scale) if column.precision && column.scale
|
60
120
|
end
|
61
121
|
|
62
|
-
def
|
63
|
-
|
122
|
+
def association
|
123
|
+
@association ||= klass.reflect_on_all_associations(:belongs_to).find do |a|
|
124
|
+
a.foreign_key.to_s == column.name
|
125
|
+
end
|
64
126
|
end
|
65
127
|
|
66
128
|
def to_hash
|
67
|
-
[:presence, :numericality, :length, :inclusion].inject({}) do |h,k|
|
129
|
+
[:presence, :numericality, :length, :inclusion].inject({}) do |h, k|
|
68
130
|
send(:"#{k}?") ? h.merge(k => send(k)) : h
|
69
131
|
end
|
70
132
|
end
|
@@ -73,4 +135,4 @@ class ValidatesBySchema::ValidationOption
|
|
73
135
|
to_hash.inspect
|
74
136
|
end
|
75
137
|
|
76
|
-
end
|
138
|
+
end
|
data/lib/validates_by_schema.rb
CHANGED
@@ -1,30 +1,62 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/lazy_load_hooks'
|
3
|
+
|
1
4
|
module ValidatesBySchema
|
2
5
|
autoload :ValidationOption, 'validates_by_schema/validation_option'
|
3
6
|
|
4
7
|
extend ActiveSupport::Concern
|
5
8
|
|
9
|
+
mattr_accessor :validate_uniqueness
|
10
|
+
self.validate_uniqueness = true
|
11
|
+
|
6
12
|
module ClassMethods
|
7
|
-
def validates_by_schema options={}
|
8
|
-
return unless table_exists?
|
9
|
-
columns = schema_validateable_columns
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
def validates_by_schema(options = {})
|
15
|
+
@validates_by_schema_options = options
|
16
|
+
define_schema_validations if schema_loaded?
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_schema!
|
20
|
+
super
|
21
|
+
# define schema validations lazy to avoid accessing the database
|
22
|
+
# at class load time.
|
23
|
+
define_schema_validations
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def define_schema_validations
|
29
|
+
return unless @validates_by_schema_options
|
30
|
+
|
31
|
+
customized_schema_validatable_columns.each do |c|
|
32
|
+
ValidationOption.new(self, c).define!
|
14
33
|
end
|
15
34
|
|
16
|
-
|
17
|
-
|
18
|
-
|
35
|
+
@validates_by_schema_options = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def customized_schema_validatable_columns
|
39
|
+
# Allow user to specify :only or :except options
|
40
|
+
schema_validatable_columns.tap do |columns|
|
41
|
+
{ only: :select!, except: :reject! }.each do |k, v|
|
42
|
+
if @validates_by_schema_options[k]
|
43
|
+
attrs = Array(@validates_by_schema_options[k]).collect(&:to_s)
|
44
|
+
columns.send(v) { |c| attrs.include?(c.name) }
|
45
|
+
end
|
46
|
+
end
|
19
47
|
end
|
20
48
|
end
|
21
49
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
c.primary || %w(updated_at created_at).include?(c.name)
|
50
|
+
def schema_validatable_columns
|
51
|
+
columns.reject do |c|
|
52
|
+
ignored_columns_for_validates_by_schema.include?(c.name)
|
26
53
|
end
|
27
54
|
end
|
55
|
+
|
56
|
+
def ignored_columns_for_validates_by_schema
|
57
|
+
[primary_key.to_s, 'created_at', 'updated_at', 'deleted_at']
|
58
|
+
end
|
59
|
+
|
28
60
|
end
|
29
61
|
end
|
30
62
|
|
@@ -0,0 +1,118 @@
|
|
1
|
+
PATH
|
2
|
+
remote: ../..
|
3
|
+
specs:
|
4
|
+
validates_by_schema (0.4.0)
|
5
|
+
activerecord (>= 5.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionpack (5.0.7.2)
|
11
|
+
actionview (= 5.0.7.2)
|
12
|
+
activesupport (= 5.0.7.2)
|
13
|
+
rack (~> 2.0)
|
14
|
+
rack-test (~> 0.6.3)
|
15
|
+
rails-dom-testing (~> 2.0)
|
16
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
17
|
+
actionview (5.0.7.2)
|
18
|
+
activesupport (= 5.0.7.2)
|
19
|
+
builder (~> 3.1)
|
20
|
+
erubis (~> 2.7.0)
|
21
|
+
rails-dom-testing (~> 2.0)
|
22
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
23
|
+
activemodel (5.0.7.2)
|
24
|
+
activesupport (= 5.0.7.2)
|
25
|
+
activerecord (5.0.7.2)
|
26
|
+
activemodel (= 5.0.7.2)
|
27
|
+
activesupport (= 5.0.7.2)
|
28
|
+
arel (~> 7.0)
|
29
|
+
activesupport (5.0.7.2)
|
30
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
31
|
+
i18n (>= 0.7, < 2)
|
32
|
+
minitest (~> 5.1)
|
33
|
+
tzinfo (~> 1.1)
|
34
|
+
arel (7.1.4)
|
35
|
+
builder (3.2.4)
|
36
|
+
concurrent-ruby (1.1.9)
|
37
|
+
crass (1.0.6)
|
38
|
+
diff-lcs (1.4.4)
|
39
|
+
docile (1.4.0)
|
40
|
+
erubis (2.7.0)
|
41
|
+
i18n (1.8.11)
|
42
|
+
concurrent-ruby (~> 1.0)
|
43
|
+
loofah (2.12.0)
|
44
|
+
crass (~> 1.0.2)
|
45
|
+
nokogiri (>= 1.5.9)
|
46
|
+
method_source (1.0.0)
|
47
|
+
mini_portile2 (2.6.1)
|
48
|
+
minitest (5.14.4)
|
49
|
+
mysql2 (0.5.3)
|
50
|
+
nokogiri (1.12.5)
|
51
|
+
mini_portile2 (~> 2.6.1)
|
52
|
+
racc (~> 1.4)
|
53
|
+
pg (1.2.3)
|
54
|
+
racc (1.6.0)
|
55
|
+
rack (2.2.3)
|
56
|
+
rack-test (0.6.3)
|
57
|
+
rack (>= 1.0)
|
58
|
+
rails-dom-testing (2.0.3)
|
59
|
+
activesupport (>= 4.2.0)
|
60
|
+
nokogiri (>= 1.6)
|
61
|
+
rails-html-sanitizer (1.4.2)
|
62
|
+
loofah (~> 2.3)
|
63
|
+
railties (5.0.7.2)
|
64
|
+
actionpack (= 5.0.7.2)
|
65
|
+
activesupport (= 5.0.7.2)
|
66
|
+
method_source
|
67
|
+
rake (>= 0.8.7)
|
68
|
+
thor (>= 0.18.1, < 2.0)
|
69
|
+
rake (13.0.6)
|
70
|
+
rspec-core (3.10.1)
|
71
|
+
rspec-support (~> 3.10.0)
|
72
|
+
rspec-expectations (3.10.1)
|
73
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
74
|
+
rspec-support (~> 3.10.0)
|
75
|
+
rspec-mocks (3.10.2)
|
76
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
77
|
+
rspec-support (~> 3.10.0)
|
78
|
+
rspec-rails (4.1.2)
|
79
|
+
actionpack (>= 4.2)
|
80
|
+
activesupport (>= 4.2)
|
81
|
+
railties (>= 4.2)
|
82
|
+
rspec-core (~> 3.10)
|
83
|
+
rspec-expectations (~> 3.10)
|
84
|
+
rspec-mocks (~> 3.10)
|
85
|
+
rspec-support (~> 3.10)
|
86
|
+
rspec-support (3.10.3)
|
87
|
+
shoulda-matchers (4.5.1)
|
88
|
+
activesupport (>= 4.2.0)
|
89
|
+
simplecov (0.21.2)
|
90
|
+
docile (~> 1.1)
|
91
|
+
simplecov-html (~> 0.11)
|
92
|
+
simplecov_json_formatter (~> 0.1)
|
93
|
+
simplecov-html (0.12.3)
|
94
|
+
simplecov-lcov (0.8.0)
|
95
|
+
simplecov_json_formatter (0.1.3)
|
96
|
+
sqlite3 (1.3.13)
|
97
|
+
thor (1.1.0)
|
98
|
+
thread_safe (0.3.6)
|
99
|
+
tzinfo (1.2.9)
|
100
|
+
thread_safe (~> 0.1)
|
101
|
+
|
102
|
+
PLATFORMS
|
103
|
+
ruby
|
104
|
+
|
105
|
+
DEPENDENCIES
|
106
|
+
activerecord (~> 5.0.0)
|
107
|
+
mysql2
|
108
|
+
pg
|
109
|
+
rake
|
110
|
+
rspec-rails
|
111
|
+
shoulda-matchers
|
112
|
+
simplecov
|
113
|
+
simplecov-lcov
|
114
|
+
sqlite3 (~> 1.3.7)
|
115
|
+
validates_by_schema!
|
116
|
+
|
117
|
+
BUNDLED WITH
|
118
|
+
2.1.4
|
@@ -0,0 +1,31 @@
|
|
1
|
+
sqlite: &sqlite
|
2
|
+
adapter: sqlite3
|
3
|
+
database: spec/db/<%= ENV["RAILS_ENV"] %>.sqlite3
|
4
|
+
|
5
|
+
mysql: &mysql
|
6
|
+
adapter: mysql2
|
7
|
+
username: root
|
8
|
+
password:
|
9
|
+
database: validates_by_schema_<%= ENV["RAILS_ENV"] %>
|
10
|
+
|
11
|
+
postgresql: &postgresql
|
12
|
+
adapter: postgresql
|
13
|
+
username: postgres
|
14
|
+
password: postgres
|
15
|
+
database: validates_by_schema_<%= ENV["RAILS_ENV"] %>
|
16
|
+
min_messages: ERROR
|
17
|
+
|
18
|
+
defaults: &defaults
|
19
|
+
pool: 5
|
20
|
+
timeout: 5000
|
21
|
+
host: '127.0.0.1'
|
22
|
+
<<: *<%= ENV['DB'] || "sqlite" %>
|
23
|
+
|
24
|
+
development:
|
25
|
+
<<: *defaults
|
26
|
+
|
27
|
+
test:
|
28
|
+
<<: *defaults
|
29
|
+
|
30
|
+
production:
|
31
|
+
<<: *defaults
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# This file is auto-generated from the current state of the database. Instead
|
3
|
+
# of editing this file, please use the migrations feature of Active Record to
|
4
|
+
# incrementally modify your database, and then regenerate this schema definition.
|
5
|
+
#
|
6
|
+
# Note that this schema.rb definition is the authoritative source for your
|
7
|
+
# database schema. If you need to create the application database on another
|
8
|
+
# system, you should be using db:schema:load, not running all the migrations
|
9
|
+
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
10
|
+
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
11
|
+
#
|
12
|
+
# It's strongly recommended to check this file into your version control system.
|
13
|
+
|
14
|
+
ActiveRecord::Schema.define(version: 20_121_210_034_140) do
|
15
|
+
create_table 'widgets', force: true do |t|
|
16
|
+
t.string 'name', limit: 50
|
17
|
+
t.string 'model', null: false
|
18
|
+
t.text 'description'
|
19
|
+
t.integer 'wheels', null: false
|
20
|
+
t.integer 'doors', limit: 2
|
21
|
+
t.decimal 'price', precision: 6, scale: 2
|
22
|
+
t.decimal 'cost', precision: 4, scale: 2, null: false
|
23
|
+
t.decimal 'completion'
|
24
|
+
t.float 'rating'
|
25
|
+
t.float 'score', null: false
|
26
|
+
t.datetime 'published_at'
|
27
|
+
t.date 'invented_on'
|
28
|
+
t.time 'startup_time'
|
29
|
+
t.time 'shutdown_time', null: false
|
30
|
+
t.boolean 'enabled'
|
31
|
+
t.binary 'data'
|
32
|
+
t.integer 'parent_id', null: false
|
33
|
+
t.integer 'other_id'
|
34
|
+
t.integer 'kind', null: false
|
35
|
+
t.string 'list', array: true, limit: 3
|
36
|
+
|
37
|
+
t.index 'parent_id'
|
38
|
+
t.index ['name', 'wheels'], unique: true
|
39
|
+
t.index 'other_id', unique: true
|
40
|
+
t.index ['doors'], unique: true, where: 'enabled = true'
|
41
|
+
t.index ['model', 'price'], unique: true
|
42
|
+
t.index ['model', 'cost'], unique: true
|
43
|
+
end
|
44
|
+
end
|
Binary file
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
require 'active_record'
|
5
|
+
require 'shoulda-matchers'
|
6
|
+
require 'simplecov'
|
7
|
+
|
8
|
+
SimpleCov.start do
|
9
|
+
if ENV['CI']
|
10
|
+
require 'simplecov-lcov'
|
11
|
+
|
12
|
+
SimpleCov::Formatter::LcovFormatter.config do |c|
|
13
|
+
c.report_with_single_file = true
|
14
|
+
c.single_report_path = 'coverage/lcov.info'
|
15
|
+
end
|
16
|
+
|
17
|
+
formatter SimpleCov::Formatter::LcovFormatter
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Load up our code
|
22
|
+
require 'validates_by_schema'
|
23
|
+
|
24
|
+
# Setup the database
|
25
|
+
conf = YAML.load(ERB.new(File.read(File.join(File.dirname(__FILE__), 'config', 'database.yml'))).result)
|
26
|
+
ActiveRecord::Base.establish_connection(conf['test'])
|
27
|
+
|
28
|
+
ActiveRecord::Base.belongs_to_required_by_default = false
|
29
|
+
|
30
|
+
load(File.join(File.dirname(__FILE__), 'config', 'schema.rb'))
|
31
|
+
|
32
|
+
# Add support test models to the load path.
|
33
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'support', 'models'))
|
34
|
+
|
35
|
+
# Require all support files
|
36
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
37
|
+
|
38
|
+
RSpec.configure do |config|
|
39
|
+
config.around do |example|
|
40
|
+
ActiveRecord::Base.transaction do
|
41
|
+
example.run
|
42
|
+
raise ActiveRecord::Rollback
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Run specs in random order to surface order dependencies. If you find an
|
47
|
+
# order dependency and want to debug it, you can fix the order by providing
|
48
|
+
# the seed, which is printed after each run.
|
49
|
+
# --seed 1234
|
50
|
+
config.order = 'random'
|
51
|
+
|
52
|
+
config.include(Shoulda::Matchers::ActiveModel)
|
53
|
+
config.include(Shoulda::Matchers::ActiveRecord)
|
54
|
+
end
|
55
|
+
|
56
|
+
Shoulda::Matchers.configure do |config|
|
57
|
+
config.integrate do |with|
|
58
|
+
with.test_framework :rspec
|
59
|
+
with.library :active_record
|
60
|
+
with.library :active_model
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module Shoulda
|
65
|
+
module Matchers
|
66
|
+
module ActiveModel
|
67
|
+
class ValidatePresenceOfMatcher < ValidationMatcher
|
68
|
+
# monkey patch to use `include?` instead of `in?`
|
69
|
+
def collection_association?
|
70
|
+
association? && %i[has_many has_and_belongs_to_many].include?(association_reflection.macro)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'validates by schema' do
|
4
|
+
let(:attributes) do
|
5
|
+
{
|
6
|
+
name: 'Secret',
|
7
|
+
model: 'secret-42',
|
8
|
+
description: 'Life, the Universe, Everything',
|
9
|
+
wheels: 4,
|
10
|
+
doors: 2,
|
11
|
+
price: 4242.42,
|
12
|
+
cost: 42.42,
|
13
|
+
completion: 0.424,
|
14
|
+
rating: 42.4242,
|
15
|
+
score: 4242.42,
|
16
|
+
published_at: Time.now.to_datetime,
|
17
|
+
invented_on: Time.now.to_date,
|
18
|
+
startup_time: 1.hour.ago,
|
19
|
+
shutdown_time: 4.hours.from_now,
|
20
|
+
enabled: true,
|
21
|
+
data: 'the question'.unpack('b*').to_s,
|
22
|
+
parent: Widget.new,
|
23
|
+
kind: 'one',
|
24
|
+
list: ['abc']
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'plain' do
|
29
|
+
subject { Contraption.new attributes }
|
30
|
+
|
31
|
+
context 'validates columns of type' do
|
32
|
+
context :string do
|
33
|
+
it { should_not validate_presence_of(:name) }
|
34
|
+
it { should validate_length_of(:name).is_at_most(50) }
|
35
|
+
it { should validate_presence_of(:model) }
|
36
|
+
if ENV['DB'] == 'mysql'
|
37
|
+
it { should validate_length_of(:model).is_at_most(255) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context :text do
|
42
|
+
it { should_not validate_presence_of(:description) }
|
43
|
+
it { should_not validate_length_of(:description).is_at_most(10_000_000) }
|
44
|
+
end
|
45
|
+
|
46
|
+
context :primary_key do
|
47
|
+
it { should_not validate_presence_of(:id) }
|
48
|
+
end
|
49
|
+
|
50
|
+
context :integer do
|
51
|
+
it { should validate_presence_of(:wheels) }
|
52
|
+
it { should validate_numericality_of(:wheels).only_integer }
|
53
|
+
it { should allow_value(242_424).for(:wheels) }
|
54
|
+
it { should allow_value(-242_424).for(:wheels) }
|
55
|
+
if ENV['DB'] != 'mysql'
|
56
|
+
it { should allow_value(2_147_483_647).for(:wheels) }
|
57
|
+
it { should allow_value(-2_147_483_647).for(:wheels) }
|
58
|
+
if ENV['DB'] != 'postgresql' && ActiveRecord.version.to_s >= '5.1'
|
59
|
+
it { should allow_value(10**100).for(:wheels) }
|
60
|
+
it { should allow_value(-10**100).for(:wheels) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it { should_not validate_presence_of(:doors) }
|
65
|
+
it { should validate_numericality_of(:doors).only_integer }
|
66
|
+
it { should allow_value(32767).for(:doors) }
|
67
|
+
it { should allow_value(-32767).for(:doors) }
|
68
|
+
it { should_not allow_value(32768).for(:doors) }
|
69
|
+
it { should_not allow_value(-32768).for(:doors) }
|
70
|
+
end
|
71
|
+
|
72
|
+
context :decimal do
|
73
|
+
it { should_not validate_presence_of(:price) }
|
74
|
+
it { should validate_numericality_of(:price) }
|
75
|
+
it { should allow_value(-9_999.99).for(:price) }
|
76
|
+
it { should allow_value(9_999.99).for(:price) }
|
77
|
+
it { should_not allow_value(10_000).for(:price) }
|
78
|
+
it { should_not allow_value(-10_000).for(:price) }
|
79
|
+
|
80
|
+
it { should validate_presence_of(:cost) }
|
81
|
+
it { should validate_numericality_of(:cost) }
|
82
|
+
it { should allow_value(99.99).for(:cost) }
|
83
|
+
it { should allow_value(-99.99).for(:cost) }
|
84
|
+
it { should_not allow_value(100).for(:cost) }
|
85
|
+
it { should_not allow_value(-100).for(:cost) }
|
86
|
+
end
|
87
|
+
|
88
|
+
context :float do
|
89
|
+
it { should_not validate_presence_of(:rating) }
|
90
|
+
it { should validate_numericality_of(:rating) }
|
91
|
+
it { should allow_value(242_424.242424).for(:rating) }
|
92
|
+
it { should allow_value(-5).for(:rating) }
|
93
|
+
|
94
|
+
it { should validate_presence_of(:score) }
|
95
|
+
it { should validate_numericality_of(:score) }
|
96
|
+
it { should allow_value(242_424.242424).for(:score) }
|
97
|
+
it { should allow_value(-5).for(:score) }
|
98
|
+
end
|
99
|
+
|
100
|
+
context :datetime do
|
101
|
+
it { should_not validate_presence_of(:published_at) }
|
102
|
+
end
|
103
|
+
|
104
|
+
context :date do
|
105
|
+
it { should_not validate_presence_of(:invented_on) }
|
106
|
+
end
|
107
|
+
|
108
|
+
context :time do
|
109
|
+
it { should_not validate_presence_of(:startup_time) }
|
110
|
+
it { should validate_presence_of(:shutdown_time) }
|
111
|
+
end
|
112
|
+
|
113
|
+
context :belongs_to do
|
114
|
+
if !ActiveRecord::Base.belongs_to_required_by_default
|
115
|
+
# belongs_to_required_by_default produces message 'must exist' instead of 'can't be blank'
|
116
|
+
it { should validate_presence_of(:parent) }
|
117
|
+
end
|
118
|
+
|
119
|
+
it { should allow_value(Widget.new).for(:parent) }
|
120
|
+
it { should_not allow_value(nil).for(:parent) }
|
121
|
+
end
|
122
|
+
|
123
|
+
context :enum do
|
124
|
+
it { should validate_presence_of(:kind) }
|
125
|
+
it { should allow_value('other').for(:kind) }
|
126
|
+
end
|
127
|
+
|
128
|
+
if ENV['DB'] == 'postgresql'
|
129
|
+
context :array do
|
130
|
+
it { should allow_value(['abc', 'def', 'ghi', 'jkl']).for(:list) }
|
131
|
+
# this value will be rejected by the db, but the validation would require a custom
|
132
|
+
# validator.
|
133
|
+
it { should allow_value(['abcdef']).for(:list) }
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context 'validates uniqueness' do
|
139
|
+
let(:valid_attributes) do
|
140
|
+
attrs = attributes.dup
|
141
|
+
|
142
|
+
attrs[:list] = nil
|
143
|
+
attrs[:parent_id] = 23
|
144
|
+
attrs[:other_id] = 42
|
145
|
+
attrs[:wheels] = 3
|
146
|
+
attrs[:name] = 'Geheimnis'
|
147
|
+
attrs[:model] = 'secret-23'
|
148
|
+
attrs[:cost] = 9.99
|
149
|
+
attrs[:price] = 20.01
|
150
|
+
|
151
|
+
attrs
|
152
|
+
end
|
153
|
+
let(:existing_widget) { Widget.new(valid_attributes) }
|
154
|
+
before do
|
155
|
+
subject.list = nil
|
156
|
+
existing_widget.save!
|
157
|
+
end
|
158
|
+
|
159
|
+
context :simple_non_unique_index do
|
160
|
+
context 'has assumptions' do
|
161
|
+
it { expect(existing_widget.parent_id).to eq 23 }
|
162
|
+
end
|
163
|
+
|
164
|
+
it { should_not validate_uniqueness_of(:parent_id) }
|
165
|
+
it { should allow_value(23).for(:parent_id) }
|
166
|
+
end
|
167
|
+
|
168
|
+
context :simple_unique_index do
|
169
|
+
context 'has assumptions' do
|
170
|
+
it { expect(existing_widget.other_id).to eq 42 }
|
171
|
+
end
|
172
|
+
|
173
|
+
it { should validate_uniqueness_of(:other_id) }
|
174
|
+
it { should_not allow_value(42).for(:other_id) }
|
175
|
+
it { should allow_value(43).for(:other_id) }
|
176
|
+
end
|
177
|
+
|
178
|
+
context :multi_column_unique_index do
|
179
|
+
before { existing_widget.update!(wheels: 4) }
|
180
|
+
|
181
|
+
context 'has assumptions' do
|
182
|
+
it { expect(existing_widget.name).to eq 'Geheimnis' }
|
183
|
+
it { expect(existing_widget.wheels).to eq 4 }
|
184
|
+
it { expect(subject.wheels).to eq 4 }
|
185
|
+
end
|
186
|
+
|
187
|
+
it { should validate_uniqueness_of(:name).scoped_to(:wheels).ignoring_case_sensitivity }
|
188
|
+
it { should_not allow_value('Geheimnis').for(:name) }
|
189
|
+
it do
|
190
|
+
subject.wheels = 3
|
191
|
+
should allow_value('Geheimnis').for(:name)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
context :multiple_multi_column_unique_index do
|
196
|
+
before { existing_widget.update!(cost: 10.0, price: 20.0) }
|
197
|
+
|
198
|
+
context 'has assumptions' do
|
199
|
+
it { expect(existing_widget.model).to eq 'secret-23' }
|
200
|
+
it { expect(existing_widget.cost).to eq 10.0 }
|
201
|
+
it { expect(existing_widget.price).to eq 20.0 }
|
202
|
+
it { expect(subject.cost).to eq 42.42 }
|
203
|
+
it { expect(subject.price).to eq 4242.42 }
|
204
|
+
end
|
205
|
+
|
206
|
+
it { should allow_value('secret-23').for(:model) }
|
207
|
+
it do
|
208
|
+
subject.cost = 10.0
|
209
|
+
should_not allow_value('secret-23').for(:model)
|
210
|
+
end
|
211
|
+
it do
|
212
|
+
subject.cost = 10.0
|
213
|
+
subject.price = 20.0
|
214
|
+
should_not allow_value('secret-23').for(:model)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
if ENV['DB'] == 'postgresql'
|
219
|
+
context :partial_unique_index do
|
220
|
+
context 'has assumptions' do
|
221
|
+
it { expect(existing_widget.doors).to eq 2 }
|
222
|
+
it { expect(existing_widget).to be_enabled }
|
223
|
+
it { should be_enabled }
|
224
|
+
end
|
225
|
+
|
226
|
+
it { should validate_uniqueness_of(:doors) }
|
227
|
+
it { should_not allow_value(2).for(:doors) }
|
228
|
+
it do
|
229
|
+
existing_widget.update(enabled: false)
|
230
|
+
should allow_value(2).for(:doors)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context 'with except' do
|
238
|
+
subject { Gadget.new attributes }
|
239
|
+
|
240
|
+
context 'does not validate passed columns' do
|
241
|
+
context :string do
|
242
|
+
it { should_not validate_presence_of(:name) }
|
243
|
+
it { should validate_length_of(:name).is_at_most(50) }
|
244
|
+
end
|
245
|
+
|
246
|
+
context :integer do
|
247
|
+
it { should_not validate_presence_of(:wheels) }
|
248
|
+
it { should_not validate_numericality_of(:wheels) }
|
249
|
+
it { should allow_value(242_424).for(:wheels) }
|
250
|
+
it { should allow_value(-42_424).for(:wheels) }
|
251
|
+
if ENV['DB'] == 'postgresql' && ActiveRecord.version.to_s >= '5.1'
|
252
|
+
# Indexed int colums produce ActiveModel::RangeError in Rails 5
|
253
|
+
it { should allow_value(10**100).for(:wheels) }
|
254
|
+
it { should allow_value(-10**100).for(:wheels) }
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context :decimal do
|
259
|
+
it { should_not validate_presence_of(:price) }
|
260
|
+
it { should validate_numericality_of(:price) }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
context 'with only' do
|
266
|
+
subject { Gizmo.new attributes }
|
267
|
+
|
268
|
+
context 'validate just passed columns' do
|
269
|
+
context :string do
|
270
|
+
it { should_not validate_presence_of(:name) }
|
271
|
+
it { should validate_length_of(:name).is_at_most(50) }
|
272
|
+
end
|
273
|
+
|
274
|
+
context :integer do
|
275
|
+
it { should validate_presence_of(:wheels) }
|
276
|
+
it { should validate_numericality_of(:wheels).only_integer }
|
277
|
+
it { should allow_value(242_424).for(:wheels) }
|
278
|
+
it { should allow_value(-42_424).for(:wheels) }
|
279
|
+
if ENV['DB'] != 'mysql'
|
280
|
+
it { should allow_value(2_147_483_647).for(:wheels) }
|
281
|
+
it { should allow_value(-2_147_483_647).for(:wheels) }
|
282
|
+
if ENV['DB'] != 'postgresql' && ActiveRecord.version.to_s >= '5.1'
|
283
|
+
it { should allow_value(10**100).for(:wheels) }
|
284
|
+
it { should allow_value(-10**100).for(:wheels) }
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
context :decimal do
|
290
|
+
it { should_not validate_presence_of(:price) }
|
291
|
+
it { should_not validate_numericality_of(:price) }
|
292
|
+
it { should allow_value('1000000').for(:price) }
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
context 'subclass' do
|
298
|
+
subject { SubContraption.new attributes }
|
299
|
+
|
300
|
+
context 'performs validations as well' do
|
301
|
+
context :string do
|
302
|
+
it { should_not validate_presence_of(:name) }
|
303
|
+
it { should validate_length_of(:name).is_at_most(50) }
|
304
|
+
it { should validate_presence_of(:model) }
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
context 'multiple threads' do
|
310
|
+
subject { Contraption.new attributes }
|
311
|
+
|
312
|
+
it 'defines validations only once' do
|
313
|
+
subject.list = nil
|
314
|
+
subject.model = nil
|
315
|
+
5.times do
|
316
|
+
Thread.new do
|
317
|
+
Contraption.new.valid?
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
expect(subject).not_to be_valid
|
322
|
+
expect(subject.errors.full_messages).to eq(["Model can't be blank"])
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
context 'reset_column_information' do
|
327
|
+
subject { Contraption.new attributes }
|
328
|
+
|
329
|
+
it 'does not duplicate validations' do
|
330
|
+
subject.list = nil
|
331
|
+
expect(subject).to be_valid
|
332
|
+
|
333
|
+
Contraption.reset_column_information
|
334
|
+
|
335
|
+
subject.model = nil
|
336
|
+
expect(subject).not_to be_valid
|
337
|
+
expect(subject.errors.full_messages).to eq(["Model can't be blank"])
|
338
|
+
end
|
339
|
+
end
|
340
|
+
end
|
metadata
CHANGED
@@ -1,151 +1,182 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: validates_by_schema
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.5.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Josh Lewis
|
8
|
+
- Pascal Zumkehr
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2021-11-25 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: activerecord
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
17
|
requirements:
|
19
|
-
- -
|
18
|
+
- - ">="
|
20
19
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
20
|
+
version: 5.0.0
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
23
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
24
|
requirements:
|
27
|
-
- -
|
25
|
+
- - ">="
|
28
26
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
27
|
+
version: 5.0.0
|
30
28
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
29
|
+
name: mysql2
|
32
30
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
31
|
requirements:
|
35
|
-
- -
|
32
|
+
- - ">="
|
36
33
|
- !ruby/object:Gem::Version
|
37
34
|
version: '0'
|
38
35
|
type: :development
|
39
36
|
prerelease: false
|
40
37
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
38
|
requirements:
|
43
|
-
- -
|
39
|
+
- - ">="
|
44
40
|
- !ruby/object:Gem::Version
|
45
41
|
version: '0'
|
46
42
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
43
|
+
name: pg
|
48
44
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
45
|
requirements:
|
51
|
-
- -
|
46
|
+
- - ">="
|
52
47
|
- !ruby/object:Gem::Version
|
53
48
|
version: '0'
|
54
49
|
type: :development
|
55
50
|
prerelease: false
|
56
51
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
52
|
requirements:
|
59
|
-
- -
|
53
|
+
- - ">="
|
60
54
|
- !ruby/object:Gem::Version
|
61
55
|
version: '0'
|
62
56
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
57
|
+
name: rake
|
64
58
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
59
|
requirements:
|
67
|
-
- -
|
60
|
+
- - ">="
|
68
61
|
- !ruby/object:Gem::Version
|
69
62
|
version: '0'
|
70
63
|
type: :development
|
71
64
|
prerelease: false
|
72
65
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
66
|
requirements:
|
75
|
-
- -
|
67
|
+
- - ">="
|
76
68
|
- !ruby/object:Gem::Version
|
77
69
|
version: '0'
|
78
70
|
- !ruby/object:Gem::Dependency
|
79
|
-
name:
|
71
|
+
name: rspec-rails
|
80
72
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
73
|
requirements:
|
83
|
-
- -
|
74
|
+
- - ">="
|
84
75
|
- !ruby/object:Gem::Version
|
85
76
|
version: '0'
|
86
77
|
type: :development
|
87
78
|
prerelease: false
|
88
79
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
80
|
requirements:
|
91
|
-
- -
|
81
|
+
- - ">="
|
92
82
|
- !ruby/object:Gem::Version
|
93
83
|
version: '0'
|
94
84
|
- !ruby/object:Gem::Dependency
|
95
|
-
name:
|
85
|
+
name: shoulda-matchers
|
96
86
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
87
|
requirements:
|
99
|
-
- -
|
88
|
+
- - ">="
|
100
89
|
- !ruby/object:Gem::Version
|
101
90
|
version: '0'
|
102
91
|
type: :development
|
103
92
|
prerelease: false
|
104
93
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
94
|
requirements:
|
107
|
-
- -
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: sqlite3
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: '0'
|
105
|
+
type: :development
|
106
|
+
prerelease: false
|
107
|
+
version_requirements: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
108
110
|
- !ruby/object:Gem::Version
|
109
111
|
version: '0'
|
110
112
|
description: Keep your code DRY by inferring column validations from table properties!
|
111
|
-
Automagically validate presence, length, numericality,
|
112
|
-
backed columns.
|
113
|
+
Automagically validate presence, length, numericality, inclusion and uniqueness
|
114
|
+
of ActiveRecord backed columns.
|
113
115
|
email:
|
114
116
|
- josh.w.lewis@gmail.com
|
117
|
+
- pascal@codez.ch
|
115
118
|
executables: []
|
116
119
|
extensions: []
|
117
120
|
extra_rdoc_files: []
|
118
121
|
files:
|
119
|
-
- lib/tasks/validates_by_schema_tasks.rake
|
120
|
-
- lib/validates_by_schema/validation_option.rb
|
121
|
-
- lib/validates_by_schema/version.rb
|
122
|
-
- lib/validates_by_schema.rb
|
123
122
|
- MIT-LICENSE
|
124
|
-
- Rakefile
|
125
123
|
- README.md
|
126
|
-
|
127
|
-
|
124
|
+
- Rakefile
|
125
|
+
- lib/validates_by_schema.rb
|
126
|
+
- lib/validates_by_schema/validation_option.rb
|
127
|
+
- lib/validates_by_schema/version.rb
|
128
|
+
- spec/ci/rails50.gemfile
|
129
|
+
- spec/ci/rails50.gemfile.lock
|
130
|
+
- spec/ci/rails52.gemfile
|
131
|
+
- spec/ci/rails60.gemfile
|
132
|
+
- spec/config/database.yml
|
133
|
+
- spec/config/schema.rb
|
134
|
+
- spec/db/test.sqlite3
|
135
|
+
- spec/spec_helper.rb
|
136
|
+
- spec/support/models/contraption.rb
|
137
|
+
- spec/support/models/gadget.rb
|
138
|
+
- spec/support/models/gizmo.rb
|
139
|
+
- spec/support/models/sub_contraption.rb
|
140
|
+
- spec/support/models/widget.rb
|
141
|
+
- spec/validations_spec.rb
|
142
|
+
homepage: https://github.com/joshwlewis/validates_by_schema
|
143
|
+
licenses:
|
144
|
+
- MIT
|
145
|
+
metadata:
|
146
|
+
homepage_uri: https://github.com/joshwlewis/validates_by_schema
|
147
|
+
source_code_uri: https://github.com/joshwlewis/validates_by_schema
|
148
|
+
changelog_uri: https://github.com/joshwlewis/validates_by_schema/blob/master/CHANGELOG.md
|
128
149
|
post_install_message:
|
129
150
|
rdoc_options: []
|
130
151
|
require_paths:
|
131
152
|
- lib
|
132
153
|
required_ruby_version: !ruby/object:Gem::Requirement
|
133
|
-
none: false
|
134
154
|
requirements:
|
135
|
-
- -
|
155
|
+
- - ">="
|
136
156
|
- !ruby/object:Gem::Version
|
137
157
|
version: '0'
|
138
158
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
-
none: false
|
140
159
|
requirements:
|
141
|
-
- -
|
160
|
+
- - ">="
|
142
161
|
- !ruby/object:Gem::Version
|
143
162
|
version: '0'
|
144
163
|
requirements: []
|
145
|
-
|
146
|
-
rubygems_version: 1.8.23
|
164
|
+
rubygems_version: 3.1.4
|
147
165
|
signing_key:
|
148
|
-
specification_version:
|
166
|
+
specification_version: 4
|
149
167
|
summary: Automatic validation based on your database schema column types and limits.
|
150
|
-
test_files:
|
151
|
-
|
168
|
+
test_files:
|
169
|
+
- spec/spec_helper.rb
|
170
|
+
- spec/db/test.sqlite3
|
171
|
+
- spec/validations_spec.rb
|
172
|
+
- spec/support/models/widget.rb
|
173
|
+
- spec/support/models/contraption.rb
|
174
|
+
- spec/support/models/sub_contraption.rb
|
175
|
+
- spec/support/models/gadget.rb
|
176
|
+
- spec/support/models/gizmo.rb
|
177
|
+
- spec/config/schema.rb
|
178
|
+
- spec/config/database.yml
|
179
|
+
- spec/ci/rails52.gemfile
|
180
|
+
- spec/ci/rails50.gemfile
|
181
|
+
- spec/ci/rails60.gemfile
|
182
|
+
- spec/ci/rails50.gemfile.lock
|