schema_expectations 0.4.0 → 0.5.0
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +6 -0
- data/lib/schema_expectations/active_record/validation_reflector.rb +4 -0
- data/lib/schema_expectations/rspec_matchers/base.rb +45 -0
- data/lib/schema_expectations/rspec_matchers/validate_schema_nullable.rb +7 -34
- data/lib/schema_expectations/rspec_matchers/validate_schema_uniqueness.rb +36 -45
- data/lib/schema_expectations/version.rb +1 -1
- data/spec/lib/schema_expectations/active_record/validation_reflector_spec.rb +10 -0
- data/spec/lib/schema_expectations/rspec_matchers/validate_schema_uniqueness_spec.rb +24 -0
- metadata +3 -3
- data/TODO.md +0 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 54b0e4a36d8ac008b802ecad39c008cc403cbdc5
|
4
|
+
data.tar.gz: 7f4d9f61aa0443b014e88f250453f16334ebea5f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 776f30431891a3cac93548c7a91f33d5ad31729174d4930679e241e87368054bc78d00a03ed5a53ac3892f1fb49791a3247b7565334f665417d08261f2b42904
|
7
|
+
data.tar.gz: e7a11f3b6707033539922eed9323302c46bbb0663d45d5d42bfdd38bc9586e9edc9dec6f5457cb00e154bfc381b49fec63fb8dad1dbd505a2253a49000b1dedb
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -8,6 +8,10 @@
|
|
8
8
|
|
9
9
|
Allows you to test whether your database schema matches the validations in your ActiveRecord models.
|
10
10
|
|
11
|
+
# Documentation
|
12
|
+
|
13
|
+
You can find documentation at http://www.rubydoc.info/gems/schema_expectations
|
14
|
+
|
11
15
|
# Installation
|
12
16
|
|
13
17
|
Add `schema_expectations` to your Gemfile:
|
@@ -60,6 +64,8 @@ note: if you exclude a column, then every unique scope which includes it will be
|
|
60
64
|
regardless of whether that scope includes other non-excluded columns. Only works similarly, in
|
61
65
|
that it will ignore any scope which contains columns not in the list
|
62
66
|
|
67
|
+
Absence validation on any attribute in a scope absolves requiring uniqueness validation.
|
68
|
+
|
63
69
|
## Validating presence constraints
|
64
70
|
|
65
71
|
The `validate_schema_nullable` matcher tests that an ActiveRecord model
|
@@ -37,6 +37,10 @@ module SchemaExpectations
|
|
37
37
|
new_with_validators validators_with_kind :presence
|
38
38
|
end
|
39
39
|
|
40
|
+
def absence
|
41
|
+
new_with_validators validators_with_kind :absence
|
42
|
+
end
|
43
|
+
|
40
44
|
def unconditional
|
41
45
|
new_with_validators validators_without_options CONDITIONAL_OPTIONS
|
42
46
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'rspec/expectations'
|
2
|
+
require 'schema_expectations/active_record/validation_reflector'
|
3
|
+
require 'schema_expectations/active_record/column_reflector'
|
4
|
+
|
5
|
+
module SchemaExpectations
|
6
|
+
module RSpecMatchers
|
7
|
+
class Base
|
8
|
+
# Specifies a list of columns to restrict matcher
|
9
|
+
#
|
10
|
+
# @return self
|
11
|
+
def only(*args)
|
12
|
+
fail 'cannot use only and except' if @except
|
13
|
+
@only = Array(args)
|
14
|
+
fail 'empty only list' if @only.empty?
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
# Specifies a list of columns for matcher to ignore
|
19
|
+
#
|
20
|
+
# @return self
|
21
|
+
def except(*args)
|
22
|
+
fail 'cannot use only and except' if @only
|
23
|
+
@except = Array(args)
|
24
|
+
fail 'empty except list' if @except.empty?
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def setup(model)
|
31
|
+
@model = cast_model model
|
32
|
+
@validation_reflector = ActiveRecord::ValidationReflector.new(@model)
|
33
|
+
@column_reflector = ActiveRecord::ColumnReflector.new(@model)
|
34
|
+
end
|
35
|
+
|
36
|
+
def cast_model(model)
|
37
|
+
model = model.class if model.is_a?(::ActiveRecord::Base)
|
38
|
+
unless model.is_a?(Class) && model.ancestors.include?(::ActiveRecord::Base)
|
39
|
+
fail "#{model.inspect} does not inherit from ActiveRecord::Base"
|
40
|
+
end
|
41
|
+
model
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'rspec/expectations'
|
2
|
-
require 'schema_expectations/
|
3
|
-
require 'schema_expectations/active_record/column_reflector'
|
2
|
+
require 'schema_expectations/rspec_matchers/base'
|
4
3
|
|
5
4
|
module SchemaExpectations
|
6
5
|
module RSpecMatchers
|
@@ -39,13 +38,9 @@ module SchemaExpectations
|
|
39
38
|
ValidateSchemaNullableMatcher.new
|
40
39
|
end
|
41
40
|
|
42
|
-
class ValidateSchemaNullableMatcher
|
41
|
+
class ValidateSchemaNullableMatcher < Base
|
43
42
|
def matches?(model)
|
44
|
-
|
45
|
-
@validation_reflector = ActiveRecord::ValidationReflector.new(@model)
|
46
|
-
@column_reflector = ActiveRecord::ColumnReflector.new(@model)
|
47
|
-
@not_null_column_names = filter_column_names(not_null_column_names).sort
|
48
|
-
@present_column_names = filter_column_names(present_column_names).sort
|
43
|
+
setup(model)
|
49
44
|
@not_null_column_names == @present_column_names
|
50
45
|
end
|
51
46
|
|
@@ -77,34 +72,12 @@ module SchemaExpectations
|
|
77
72
|
'validate NOT NULL columns are present'
|
78
73
|
end
|
79
74
|
|
80
|
-
# Specifies a list of columns to restrict matcher
|
81
|
-
#
|
82
|
-
# @return [ValidateSchemaNullableMatcher] self
|
83
|
-
def only(*args)
|
84
|
-
fail 'cannot use only and except' if @except
|
85
|
-
@only = Array(args)
|
86
|
-
fail 'empty only list' if @only.empty?
|
87
|
-
self
|
88
|
-
end
|
89
|
-
|
90
|
-
# Specifies a list of columns for matcher to ignore
|
91
|
-
#
|
92
|
-
# @return [ValidateSchemaNullableMatcher] self
|
93
|
-
def except(*args)
|
94
|
-
fail 'cannot use only and except' if @only
|
95
|
-
@except = Array(args)
|
96
|
-
fail 'empty except list' if @except.empty?
|
97
|
-
self
|
98
|
-
end
|
99
|
-
|
100
75
|
private
|
101
76
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
model
|
77
|
+
def setup(model)
|
78
|
+
super
|
79
|
+
@not_null_column_names = filter_column_names(not_null_column_names).sort
|
80
|
+
@present_column_names = filter_column_names(present_column_names).sort
|
108
81
|
end
|
109
82
|
|
110
83
|
def present_attributes
|
@@ -1,6 +1,5 @@
|
|
1
1
|
require 'rspec/expectations'
|
2
|
-
require 'schema_expectations/
|
3
|
-
require 'schema_expectations/active_record/column_reflector'
|
2
|
+
require 'schema_expectations/rspec_matchers/base'
|
4
3
|
|
5
4
|
module SchemaExpectations
|
6
5
|
module RSpecMatchers
|
@@ -16,7 +15,7 @@ module SchemaExpectations
|
|
16
15
|
# t.integer :record_id
|
17
16
|
# t.index [:record_type, :record_id], unique: true
|
18
17
|
# end
|
19
|
-
|
18
|
+
#
|
20
19
|
# class Record < ActiveRecord::Base
|
21
20
|
# validates :record_type, uniqueness: { scope: :record_id }
|
22
21
|
# end
|
@@ -38,19 +37,18 @@ module SchemaExpectations
|
|
38
37
|
# regardless of whether that scope includes other non-excluded columns. Only works similarly, in
|
39
38
|
# that it will ignore any scope which contains columns not in the list
|
40
39
|
#
|
40
|
+
# Absence validation on any attribute in a scope absolves requiring uniqueness validation.
|
41
|
+
#
|
41
42
|
# @return [ValidateSchemaUniquenessMatcher]
|
42
43
|
def validate_schema_uniqueness
|
43
44
|
ValidateSchemaUniquenessMatcher.new
|
44
45
|
end
|
45
46
|
|
46
|
-
class ValidateSchemaUniquenessMatcher
|
47
|
+
class ValidateSchemaUniquenessMatcher < Base
|
47
48
|
def matches?(model)
|
48
|
-
|
49
|
-
@
|
50
|
-
|
51
|
-
@validator_unique_scopes = filter_scopes(validator_unique_scopes).map(&:sort).sort
|
52
|
-
@schema_unique_scopes = filter_scopes(schema_unique_scopes).map(&:sort).sort
|
53
|
-
@validator_unique_scopes == @schema_unique_scopes
|
49
|
+
setup(model)
|
50
|
+
(@validator_unique_scopes - @schema_unique_scopes).empty? &&
|
51
|
+
(@schema_unique_scopes - @validator_unique_scopes - absent_scopes).empty?
|
54
52
|
end
|
55
53
|
|
56
54
|
def failure_message
|
@@ -60,7 +58,7 @@ module SchemaExpectations
|
|
60
58
|
errors << "#{@model.name} scope #{scope.inspect} has unconditional uniqueness validation but is missing a unique database index"
|
61
59
|
end
|
62
60
|
|
63
|
-
(@schema_unique_scopes - @validator_unique_scopes).each do |scope|
|
61
|
+
(@schema_unique_scopes - @validator_unique_scopes - absent_scopes).each do |scope|
|
64
62
|
conditions = validator_conditions_for_scope(scope) ||
|
65
63
|
validator_allow_empty_conditions_for_scope(scope)
|
66
64
|
if conditions
|
@@ -81,38 +79,12 @@ module SchemaExpectations
|
|
81
79
|
'validate unique indexes have uniqueness validation'
|
82
80
|
end
|
83
81
|
|
84
|
-
# Specifies a list of columns to restrict matcher
|
85
|
-
#
|
86
|
-
# Any unique scope which includes a column not in this list will be ignored
|
87
|
-
#
|
88
|
-
# @return [ValidateSchemaUniquenessMatcher] self
|
89
|
-
def only(*args)
|
90
|
-
fail 'cannot use only and except' if @except
|
91
|
-
@only = Array(args)
|
92
|
-
fail 'empty only list' if @only.empty?
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
|
-
# Specifies a list of columns for matcher to ignore
|
97
|
-
#
|
98
|
-
# Any unique scope which includes one of these columns will be ignored
|
99
|
-
#
|
100
|
-
# @return [ValidateSchemaUniquenessMatcher] self
|
101
|
-
def except(*args)
|
102
|
-
fail 'cannot use only and except' if @only
|
103
|
-
@except = Array(args)
|
104
|
-
fail 'empty except list' if @except.empty?
|
105
|
-
self
|
106
|
-
end
|
107
|
-
|
108
82
|
private
|
109
83
|
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
end
|
115
|
-
model
|
84
|
+
def setup(model)
|
85
|
+
super
|
86
|
+
@validator_unique_scopes = deep_sort(filter_scopes(validator_unique_scopes))
|
87
|
+
@schema_unique_scopes = deep_sort(filter_scopes(schema_unique_scopes))
|
116
88
|
end
|
117
89
|
|
118
90
|
def validator_unique_scopes
|
@@ -133,21 +105,40 @@ module SchemaExpectations
|
|
133
105
|
end
|
134
106
|
end
|
135
107
|
|
108
|
+
def deep_sort(scopes)
|
109
|
+
scopes.map(&:sort).sort
|
110
|
+
end
|
111
|
+
|
136
112
|
def validator_conditions_for_scope(scope)
|
137
|
-
|
138
|
-
conditions = reflector.attributes.map do |attribute|
|
113
|
+
validator_conditions(scope) do |reflector, attribute|
|
139
114
|
reflector.conditions_for_attribute attribute
|
140
115
|
end
|
141
|
-
conditions.compact.first
|
142
116
|
end
|
143
117
|
|
144
118
|
def validator_allow_empty_conditions_for_scope(scope)
|
119
|
+
validator_conditions(scope) do |reflector, attribute|
|
120
|
+
reflector.allow_empty_conditions_for_attribute attribute
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def validator_conditions(scope)
|
145
125
|
reflector = @validation_reflector.for_unique_scope(scope)
|
146
126
|
conditions = reflector.attributes.map do |attribute|
|
147
|
-
reflector
|
127
|
+
yield reflector, attribute
|
148
128
|
end
|
149
129
|
conditions.compact.first
|
150
130
|
end
|
131
|
+
|
132
|
+
def absent_scopes
|
133
|
+
scopes = @validator_unique_scopes + @schema_unique_scopes
|
134
|
+
absent_attributes = @validation_reflector.
|
135
|
+
absence.unconditional.disallow_empty.attributes
|
136
|
+
absent_columns = @column_reflector.for_attributes(*absent_attributes).column_names
|
137
|
+
|
138
|
+
scopes.reject do |scope|
|
139
|
+
(scope & absent_columns).empty?
|
140
|
+
end
|
141
|
+
end
|
151
142
|
end
|
152
143
|
end
|
153
144
|
end
|
@@ -41,6 +41,16 @@ module SchemaExpectations
|
|
41
41
|
expect(validation_reflector.presence.unconditional.disallow_nil.attributes).to eq %i(present)
|
42
42
|
end
|
43
43
|
|
44
|
+
specify '#absence', active_record_version: '>= 4.0' do
|
45
|
+
Record.instance_eval do
|
46
|
+
validates :absent, absence: true
|
47
|
+
validates :not_absent, length: { minimum: 1 }
|
48
|
+
end
|
49
|
+
|
50
|
+
expect(validation_reflector.attributes).to eq %i(absent not_absent)
|
51
|
+
expect(validation_reflector.absence.attributes).to eq %i(absent)
|
52
|
+
end
|
53
|
+
|
44
54
|
specify '#for_unique_scope' do
|
45
55
|
Record.instance_eval do
|
46
56
|
validates :a, uniqueness: true
|
@@ -122,6 +122,30 @@ describe SchemaExpectations::RSpecMatchers::ValidateSchemaUniquenessMatcher, :ac
|
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
125
|
+
context 'with absence validation on a unique column', active_record_version: '>= 4.0' do
|
126
|
+
before do
|
127
|
+
validates unique_scope.first, absence: true
|
128
|
+
end
|
129
|
+
|
130
|
+
it { is_expected.to validate_schema_uniqueness }
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'with conditional absence validation on a unique column', active_record_version: '>= 4.0' do
|
134
|
+
before do
|
135
|
+
validates unique_scope.first, absence: true, if: -> { false }
|
136
|
+
end
|
137
|
+
|
138
|
+
it { is_expected.to_not validate_schema_uniqueness }
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'with allow_blank absence validation on a unique column', active_record_version: '>= 4.0' do
|
142
|
+
before do
|
143
|
+
validates unique_scope.first, absence: true, allow_blank: true
|
144
|
+
end
|
145
|
+
|
146
|
+
it { is_expected.to_not validate_schema_uniqueness }
|
147
|
+
end
|
148
|
+
|
125
149
|
specify '#failure_message_when_negated' do
|
126
150
|
validates unique_scope.first, uniqueness: { scope: unique_scope.drop(1) }
|
127
151
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema_expectations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emma Borhanian
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02
|
11
|
+
date: 2015-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -262,7 +262,6 @@ files:
|
|
262
262
|
- MIT-LICENSE
|
263
263
|
- README.md
|
264
264
|
- Rakefile
|
265
|
-
- TODO.md
|
266
265
|
- gemfiles/activerecord_3.1.gemfile
|
267
266
|
- gemfiles/activerecord_3.2.gemfile
|
268
267
|
- gemfiles/activerecord_4.0.gemfile
|
@@ -278,6 +277,7 @@ files:
|
|
278
277
|
- lib/schema_expectations/config.rb
|
279
278
|
- lib/schema_expectations/rspec.rb
|
280
279
|
- lib/schema_expectations/rspec_matchers.rb
|
280
|
+
- lib/schema_expectations/rspec_matchers/base.rb
|
281
281
|
- lib/schema_expectations/rspec_matchers/validate_schema_nullable.rb
|
282
282
|
- lib/schema_expectations/rspec_matchers/validate_schema_uniqueness.rb
|
283
283
|
- lib/schema_expectations/util.rb
|
data/TODO.md
DELETED