schema_expectations 0.4.0 → 0.5.0

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
  SHA1:
3
- metadata.gz: de04e4a86866e356d2225b0b4236407b34a0795c
4
- data.tar.gz: 5127ab4ef41da535ff54de06928081ca9c887e63
3
+ metadata.gz: 54b0e4a36d8ac008b802ecad39c008cc403cbdc5
4
+ data.tar.gz: 7f4d9f61aa0443b014e88f250453f16334ebea5f
5
5
  SHA512:
6
- metadata.gz: 35aa646038fd59a9e88c76d6f8698a5669a0c602f37eaad56e95c772059bfe1de37087253726f15c7301c5b8f5abec6ba00fcedbb620dfe1480995008975d129
7
- data.tar.gz: 71f5edb70e04ea2ab32c47c09ffd2132798ee8f57be0454481c2a7d0559a883051bb747a4ea65d9098fffbf495f22242deb3527410446ca1df583a1207988f49
6
+ metadata.gz: 776f30431891a3cac93548c7a91f33d5ad31729174d4930679e241e87368054bc78d00a03ed5a53ac3892f1fb49791a3247b7565334f665417d08261f2b42904
7
+ data.tar.gz: e7a11f3b6707033539922eed9323302c46bbb0663d45d5d42bfdd38bc9586e9edc9dec6f5457cb00e154bfc381b49fec63fb8dad1dbd505a2253a49000b1dedb
data/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  ### git master
4
4
 
5
+ ### 0.5.0 (March 2, 2015)
6
+
7
+ - absence validation absolves requiring uniqueness validation
8
+
5
9
  ### 0.4.0 (Febuary 20, 2015)
6
10
 
7
11
  - include matchers in non-model specs as well
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/active_record/validation_reflector'
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
- @model = cast_model model
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 cast_model(model)
103
- model = model.class if model.is_a?(::ActiveRecord::Base)
104
- unless model.is_a?(Class) && model.ancestors.include?(::ActiveRecord::Base)
105
- fail "#{model.inspect} does not inherit from ActiveRecord::Base"
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/active_record/validation_reflector'
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
- @model = cast_model model
49
- @validation_reflector = ActiveRecord::ValidationReflector.new(@model)
50
- @column_reflector = ActiveRecord::ColumnReflector.new(@model)
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 cast_model(model)
111
- model = model.class if model.is_a?(::ActiveRecord::Base)
112
- unless model.is_a?(Class) && model.ancestors.include?(::ActiveRecord::Base)
113
- fail "#{model.inspect} does not inherit from ActiveRecord::Base"
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
- reflector = @validation_reflector.for_unique_scope(scope)
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.allow_empty_conditions_for_attribute attribute
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
@@ -1,3 +1,3 @@
1
1
  module SchemaExpectations
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  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.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-21 00:00:00.000000000 Z
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
@@ -1,2 +0,0 @@
1
- - `validates :attribute, if: :attribute_changed?` should have an exception as not conditional
2
- - `:absence` validation should absolve a model of needing uniqueness validation on the relevant column