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