schema_expectations 0.0.1 → 0.2.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -0
  3. data/.travis.yml +19 -5
  4. data/Appraisals +33 -0
  5. data/CHANGELOG.md +17 -1
  6. data/README.md +4 -0
  7. data/Rakefile +12 -0
  8. data/gemfiles/activerecord_3.1.gemfile +7 -0
  9. data/gemfiles/activerecord_3.2.gemfile +7 -0
  10. data/gemfiles/activerecord_4.0.gemfile +7 -0
  11. data/gemfiles/activerecord_4.1.gemfile +7 -0
  12. data/gemfiles/activerecord_4.2.gemfile +7 -0
  13. data/gemfiles/default.gemfile +5 -0
  14. data/gemfiles/rspec_3.0.gemfile +7 -0
  15. data/gemfiles/rspec_3.1.gemfile +7 -0
  16. data/gemfiles/rspec_3.2.gemfile +7 -0
  17. data/lib/schema_expectations/active_record/column_reflector.rb +92 -0
  18. data/lib/schema_expectations/active_record/validation_reflector.rb +59 -0
  19. data/lib/schema_expectations/config.rb +25 -0
  20. data/lib/schema_expectations/rspec_matchers/validate_schema_nullable.rb +49 -58
  21. data/lib/schema_expectations/util.rb +9 -0
  22. data/lib/schema_expectations/version.rb +1 -1
  23. data/lib/schema_expectations.rb +1 -0
  24. data/schema_expectations.gemspec +12 -3
  25. data/spec/db/database.yml +13 -0
  26. data/spec/lib/schema_expectations/active_record/column_reflector_spec.rb +150 -0
  27. data/spec/lib/schema_expectations/active_record/validation_reflector_spec.rb +62 -0
  28. data/spec/lib/schema_expectations/config_spec.rb +22 -0
  29. data/spec/lib/schema_expectations/rspec_matchers/validate_schema_nullable_spec.rb +260 -77
  30. data/spec/lib/schema_expectations/util_spec.rb +16 -0
  31. data/spec/meta_spec.rb +44 -0
  32. data/spec/spec_helper.rb +11 -2
  33. data/spec/support/active_record.rb +34 -12
  34. data/spec/support/active_record_helpers.rb +51 -0
  35. data/spec/support/gem_filters.rb +6 -0
  36. metadata +162 -10
@@ -0,0 +1,150 @@
1
+ require 'spec_helper'
2
+ require 'schema_expectations/active_record/column_reflector'
3
+
4
+ module SchemaExpectations
5
+ module ActiveRecord
6
+ describe ColumnReflector, :active_record do
7
+ subject(:column_reflector) { ColumnReflector.new(Record) }
8
+
9
+ context '#without_present_default' do
10
+ specify 'filters default values' do
11
+ create_table :records do |t|
12
+ t.integer :integer_default, default: 0
13
+ t.string :string_default, default: 'test'
14
+ t.string :empty_default, default: ''
15
+ t.string :null_default, default: nil
16
+ t.string :no_default
17
+
18
+ t.timestamps null: false
19
+ end
20
+ stub_const('Record', Class.new(::ActiveRecord::Base))
21
+
22
+ expect(column_reflector.column_names).
23
+ to eq %i(id integer_default string_default empty_default null_default no_default created_at updated_at)
24
+
25
+ expect(column_reflector.without_present_default.column_names).
26
+ to eq %i(empty_default null_default no_default)
27
+ end
28
+
29
+ context 'default functions', :postgresql, active_record_version: '>= 4.0' do
30
+ specify 'are filtered' do
31
+ create_table :records do |t|
32
+ t.uuid :uuid_default, default: 'uuid_generate_v4()'
33
+ t.string :no_default
34
+ end
35
+ execute <<-SQL
36
+ -- not possible via create_table syntax right now
37
+ ALTER TABLE records
38
+ ADD COLUMN function_default float DEFAULT random();
39
+ SQL
40
+ stub_const('Record', Class.new(::ActiveRecord::Base))
41
+
42
+ expect(Record.columns_hash['function_default'].default).to be_nil
43
+ expect(Record.columns_hash['uuid_default'].default).to be_nil
44
+ expect(Record.columns_hash['function_default'].default_function).to_not be_nil
45
+ expect(Record.columns_hash['uuid_default'].default_function).to_not be_nil
46
+
47
+ expect(column_reflector.column_names).
48
+ to eq %i(id uuid_default no_default function_default)
49
+
50
+ expect(column_reflector.without_present_default.column_names).
51
+ to eq %i(no_default)
52
+ end
53
+
54
+ specify 'logs if it encounters an error trying to run default function' do
55
+ create_table :records
56
+ execute <<-SQL
57
+ CREATE FUNCTION raise_error() RETURNS varchar
58
+ AS $BODY$
59
+ BEGIN
60
+ RAISE 'test exception';
61
+ END;
62
+ $BODY$
63
+ LANGUAGE plpgsql;
64
+
65
+ -- not possible via create_table syntax right now
66
+ ALTER TABLE records
67
+ ADD COLUMN function_default varchar DEFAULT raise_error();
68
+ SQL
69
+ stub_const('Record', Class.new(::ActiveRecord::Base))
70
+
71
+ expect(Record.columns_hash['function_default'].default).to be_nil
72
+ expect(Record.columns_hash['function_default'].default_function).to_not be_nil
73
+
74
+ expect(column_reflector.column_names).
75
+ to eq %i(id function_default)
76
+
77
+ expect(SchemaExpectations.error_logger).to receive(:error).once do |message|
78
+ expect(message).to include 'SchemaExpectations: encountered error running SELECT raise_error()'
79
+ expect(message).to include 'PG::RaiseException: ERROR: test exception'
80
+ expect(message).to include ': SELECT raise_error()'
81
+ end
82
+ expect(column_reflector.without_present_default.column_names).
83
+ to eq %i(function_default)
84
+ end
85
+ end
86
+ end
87
+
88
+ specify '#not_null' do
89
+ create_table :records do |t|
90
+ t.string :not_null, null: false
91
+ t.string :nullable
92
+ end
93
+ stub_const('Record', Class.new(::ActiveRecord::Base))
94
+
95
+ expect(column_reflector.column_names).to eq %i(id not_null nullable)
96
+ expect(column_reflector.not_null.column_names).to eq %i(id not_null)
97
+ end
98
+
99
+ context '#for_attributes' do
100
+ before do
101
+ create_table :records do |t|
102
+ t.string :record_id
103
+ t.string :record_type
104
+ t.string :other
105
+ end
106
+
107
+ stub_const('Record', Class.new(::ActiveRecord::Base))
108
+ end
109
+
110
+ specify 'without associations' do
111
+ expect(column_reflector.column_names).to eq %i(id record_id record_type other)
112
+ expect(column_reflector.for_attributes(:missing).column_names).to be_empty
113
+ expect(column_reflector.for_attributes(:record).column_names).to be_empty
114
+ expect(column_reflector.for_attributes(:record_id).column_names).to eq %i(record_id)
115
+ expect(column_reflector.for_attributes(:record_type).column_names).to eq %i(record_type)
116
+ expect(column_reflector.for_attributes(:other).column_names).to eq %i(other)
117
+ expect(column_reflector.for_attributes(*%i(record missing other)).column_names).to eq %i(other)
118
+ end
119
+
120
+ specify 'belongs_to polymorphic' do
121
+ Record.instance_eval do
122
+ belongs_to :record, polymorphic: true
123
+ end
124
+
125
+ expect(column_reflector.column_names).to eq %i(id record_id record_type other)
126
+ expect(column_reflector.for_attributes(:missing).column_names).to be_empty
127
+ expect(column_reflector.for_attributes(:record).column_names).to eq %i(record_id record_type)
128
+ expect(column_reflector.for_attributes(:record_id).column_names).to eq %i(record_id)
129
+ expect(column_reflector.for_attributes(:record_type).column_names).to eq %i(record_type)
130
+ expect(column_reflector.for_attributes(:other).column_names).to eq %i(other)
131
+ expect(column_reflector.for_attributes(*%i(record missing other)).column_names).to eq %i(record_id record_type other)
132
+ end
133
+
134
+ specify 'belongs_to' do
135
+ Record.instance_eval do
136
+ belongs_to :record
137
+ end
138
+
139
+ expect(column_reflector.column_names).to eq %i(id record_id record_type other)
140
+ expect(column_reflector.for_attributes(:missing).column_names).to be_empty
141
+ expect(column_reflector.for_attributes(:record).column_names).to eq %i(record_id)
142
+ expect(column_reflector.for_attributes(:record_id).column_names).to eq %i(record_id)
143
+ expect(column_reflector.for_attributes(:record_type).column_names).to eq %i(record_type)
144
+ expect(column_reflector.for_attributes(:other).column_names).to eq %i(other)
145
+ expect(column_reflector.for_attributes(*%i(record missing other)).column_names).to eq %i(record_id other)
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+ require 'schema_expectations/active_record/validation_reflector'
3
+
4
+ module SchemaExpectations
5
+ module ActiveRecord
6
+ describe ValidationReflector, :active_record do
7
+ before do
8
+ create_table :records
9
+
10
+ stub_const('Record', Class.new(::ActiveRecord::Base))
11
+ end
12
+
13
+ subject(:validation_reflector) { ValidationReflector.new(Record) }
14
+
15
+ specify 'allows filtering attributes' do
16
+ Record.instance_eval do
17
+ validates :present, presence: true
18
+ validates :not_present, length: { minimum: 1 }
19
+ validates :conditional_1, presence: true, on: :create
20
+ validates :conditional_2, presence: true, if: ->{ false }
21
+ validates :conditional_3, presence: true, unless: ->{ false }
22
+ validates :conditional_4, presence: true, allow_nil: true
23
+ validates :conditional_5, presence: true, allow_blank: true
24
+ end
25
+
26
+ expect(validation_reflector.attributes).to eq %i(
27
+ present not_present conditional_1 conditional_2
28
+ conditional_3 conditional_4 conditional_5)
29
+
30
+ expect(validation_reflector.unconditional.attributes).to eq %i(present not_present)
31
+
32
+ expect(validation_reflector.presence.attributes).to eq %i(
33
+ present conditional_1 conditional_2
34
+ conditional_3 conditional_4 conditional_5)
35
+
36
+ expect(validation_reflector.unconditional.presence.attributes).to eq %i(present)
37
+ expect(validation_reflector.presence.unconditional.attributes).to eq %i(present)
38
+ end
39
+
40
+ specify '#conditions_for_attribute' do
41
+ Record.instance_eval do
42
+ validates :present, presence: true
43
+ validates :not_present, length: { minimum: 1 }
44
+ validates :conditional_1, presence: true, on: :create
45
+ validates :conditional_2, presence: true, if: ->{ false }
46
+ validates :conditional_3, presence: true, unless: ->{ false }
47
+ validates :conditional_4, presence: true, allow_nil: true
48
+ validates :conditional_5, presence: true, allow_blank: true
49
+ end
50
+
51
+ expect(validation_reflector.conditions_for_attribute(:missing)).to be_nil
52
+ expect(validation_reflector.conditions_for_attribute(:present)).to be_nil
53
+ expect(validation_reflector.conditions_for_attribute(:not_present)).to be_nil
54
+ expect(validation_reflector.conditions_for_attribute(:conditional_1)).to eq(on: :create)
55
+ expect(validation_reflector.conditions_for_attribute(:conditional_2).keys).to eq [:if]
56
+ expect(validation_reflector.conditions_for_attribute(:conditional_3).keys).to eq [:unless]
57
+ expect(validation_reflector.conditions_for_attribute(:conditional_4)).to eq(allow_nil: true)
58
+ expect(validation_reflector.conditions_for_attribute(:conditional_5)).to eq(allow_blank: true)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+ require 'schema_expectations/config'
3
+
4
+ module SchemaExpectations
5
+ describe Config do
6
+ after do
7
+ SchemaExpectations.configure do |config|
8
+ config.reset!
9
+ end
10
+ end
11
+
12
+ specify 'error_logger' do
13
+ expect(SchemaExpectations.error_logger).to be_a Logger
14
+
15
+ new_logger = Logger.new(StringIO.new)
16
+ SchemaExpectations.configure do |config|
17
+ config.error_logger = new_logger
18
+ end
19
+ expect(SchemaExpectations.error_logger).to be new_logger
20
+ end
21
+ end
22
+ end
@@ -1,106 +1,289 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe :validate_schema_nullable do
4
- specify 'works on instances', :active_record do
5
- create_table :records do |t|
6
- t.string :name, null: false
7
- t.string :wrong
3
+ describe SchemaExpectations::RSpecMatchers::ValidateSchemaNullableMatcher, :active_record do
4
+ shared_examples_for 'Record' do
5
+ def validates(*args)
6
+ Record.instance_eval do
7
+ validates *args
8
+ end
8
9
  end
9
10
 
10
- stub_const('Record', Class.new(ActiveRecord::Base))
11
+ let(:not_null_columns) { [:not_null] }
12
+ let(:nullable_columns) { [:nullable] }
13
+ let(:columns) { not_null_columns + nullable_columns }
11
14
 
12
- Record.instance_eval do
13
- validates :name, :wrong, presence: true
15
+ before do
16
+ create_table :records do |t|
17
+ not_null_columns.each do |column|
18
+ t.string column, null: false
19
+ end
20
+
21
+ nullable_columns.each do |column|
22
+ t.string column
23
+ end
24
+ end
25
+
26
+ stub_const('Record', Class.new(ActiveRecord::Base))
14
27
  end
15
28
 
16
- expect(Record.new).to validate_schema_nullable.only(:name)
17
- expect(Record.new).to_not validate_schema_nullable.only(:wrong)
18
- end
29
+ subject(:record) { Record }
30
+
31
+ context 'with no validations' do
32
+ it { is_expected.to_not validate_schema_nullable }
33
+
34
+ specify 'error messages' do
35
+ expect do
36
+ is_expected.to validate_schema_nullable
37
+ end.to raise_error do |error|
38
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
39
+ not_null_columns.sort.zip(error.message.split(', ')) do |column, message|
40
+ expect(message).to eq "#{column} is NOT NULL but has no presence validation"
41
+ end
42
+ end
43
+ end
44
+
45
+ specify '#only' do
46
+ is_expected.to_not validate_schema_nullable.only(*columns)
47
+ is_expected.to_not validate_schema_nullable.only(*not_null_columns)
48
+ is_expected.to validate_schema_nullable.only(*nullable_columns)
49
+ end
19
50
 
20
- specify 'doesnt raise extraneous exceptions from timestamps', :active_record do
21
- create_table :records do |t|
22
- t.timestamps
51
+ specify '#except' do
52
+ is_expected.to validate_schema_nullable.except(*columns)
53
+ is_expected.to validate_schema_nullable.except(*not_null_columns)
54
+ is_expected.to_not validate_schema_nullable.except(*nullable_columns)
55
+ end
23
56
  end
24
57
 
25
- stub_const('Record', Class.new(ActiveRecord::Base))
58
+ context 'with not_null present' do
59
+ before { validates :not_null, presence: true }
26
60
 
27
- expect(Record.new).to validate_schema_nullable
28
- end
61
+ it { is_expected.to validate_schema_nullable }
62
+
63
+ specify '#only' do
64
+ is_expected.to validate_schema_nullable.only(*columns)
65
+ is_expected.to validate_schema_nullable.only(*not_null_columns)
66
+ is_expected.to validate_schema_nullable.only(*nullable_columns)
67
+ end
68
+
69
+ specify '#except' do
70
+ is_expected.to validate_schema_nullable.except(*columns)
71
+ is_expected.to validate_schema_nullable.except(*not_null_columns)
72
+ is_expected.to validate_schema_nullable.except(*nullable_columns)
73
+ end
74
+ end
75
+
76
+ context 'with nullable present' do
77
+ before { validates :nullable, presence: true }
78
+
79
+ it { is_expected.to_not validate_schema_nullable }
80
+
81
+ specify 'error messages' do
82
+ expect do
83
+ is_expected.to validate_schema_nullable
84
+ end.to raise_error do |error|
85
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
86
+ errors = error.message.split(', ')
87
+
88
+ nullable_columns.sort.zip(errors.take(nullable_columns.size)) do |column, message|
89
+ expect(message).to eq "#{column} has unconditional presence validation but is missing NOT NULL"
90
+ end
91
+
92
+ not_null_columns.sort.zip(errors.drop(nullable_columns.size)) do |column, message|
93
+ expect(message).to eq "#{column} is NOT NULL but has no presence validation"
94
+ end
95
+ end
96
+ end
97
+
98
+ specify '#only' do
99
+ is_expected.to_not validate_schema_nullable.only(*columns)
100
+ is_expected.to_not validate_schema_nullable.only(*not_null_columns)
101
+ is_expected.to_not validate_schema_nullable.only(*nullable_columns)
102
+ end
103
+
104
+ specify '#except' do
105
+ is_expected.to validate_schema_nullable.except(*columns)
106
+ is_expected.to_not validate_schema_nullable.except(*not_null_columns)
107
+ is_expected.to_not validate_schema_nullable.except(*nullable_columns)
108
+ end
109
+ end
110
+
111
+ context 'with nullable and not_null present' do
112
+ before do
113
+ validates :not_null, presence: true
114
+ validates :nullable, presence: true
115
+ end
116
+
117
+ it { is_expected.to_not validate_schema_nullable }
118
+
119
+ specify 'error messages' do
120
+ expect do
121
+ is_expected.to validate_schema_nullable
122
+ end.to raise_error do |error|
123
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
124
+ nullable_columns.sort.zip(error.message.split(', ')) do |column, message|
125
+ expect(message).to eq "#{column} has unconditional presence validation but is missing NOT NULL"
126
+ end
127
+ end
128
+ end
129
+
130
+ specify '#only' do
131
+ is_expected.to_not validate_schema_nullable.only(*columns)
132
+ is_expected.to validate_schema_nullable.only(*not_null_columns)
133
+ is_expected.to_not validate_schema_nullable.only(*nullable_columns)
134
+ end
135
+
136
+ specify '#except' do
137
+ is_expected.to validate_schema_nullable.except(*columns)
138
+ is_expected.to_not validate_schema_nullable.except(*not_null_columns)
139
+ is_expected.to validate_schema_nullable.except(*nullable_columns)
140
+ end
141
+ end
142
+
143
+ specify '#failure_message_when_negated' do
144
+ validates :not_null, presence: true
145
+
146
+ expect do
147
+ is_expected.to_not validate_schema_nullable
148
+ end.to raise_error 'should not match NOT NULL with its presence validation but does'
149
+ end
150
+
151
+ specify 'when primary_key is not id' do
152
+ create_table :records, force: true, id: false do |t|
153
+ t.integer :pk
154
+ end
155
+ Record.reset_column_information
156
+ Record.instance_eval do
157
+ self.primary_key = 'pk'
29
158
 
30
- specify 'asserts that presence validations match NOT NULL', :active_record do
31
- create_table :records do |t|
32
- t.string :not_null, null: false
33
- t.string :not_null_present, null: false
159
+ validates :pk, presence: true
160
+ end
34
161
 
35
- t.string :nullable
36
- t.string :nullable_present
162
+ is_expected.to validate_schema_nullable
37
163
  end
38
164
 
39
- stub_const('Record', Class.new(ActiveRecord::Base))
165
+ specify 'doesnt raise extraneous exceptions from timestamps' do
166
+ create_table :records, force: true do |t|
167
+ t.timestamps null: false
168
+ end
169
+ Record.reset_column_information
40
170
 
41
- Record.instance_eval do
42
- validates :not_null_present, presence: true
43
- validates :nullable_present, presence: true
171
+ is_expected.to validate_schema_nullable
44
172
  end
45
173
 
46
- expect(Record).to validate_schema_nullable.only(:not_null_present, :nullable)
47
- expect(Record).to validate_schema_nullable.except(:not_null, :nullable_present)
174
+ context 'ignores validators with' do
175
+ specify 'on: create' do
176
+ validates :not_null, presence: true, on: :create
177
+
178
+ expect do
179
+ is_expected.to validate_schema_nullable.only(*not_null_columns)
180
+ end.to raise_error do |error|
181
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
182
+ not_null_columns.sort.zip(error.message.split(', ')) do |column, message|
183
+ expect(message).to eq "#{column} is NOT NULL but its presence validator was conditional: {:on=>:create}"
184
+ end
185
+ end
186
+ end
48
187
 
49
- expect(Record).to_not validate_schema_nullable
50
- expect(Record).to_not validate_schema_nullable.only(:not_null)
51
- expect(Record).to_not validate_schema_nullable.only(:nullable_present)
188
+ specify 'if: proc' do
189
+ validates :not_null, presence: true, if: ->{ false }
52
190
 
53
- expect do
54
- expect(Record).to validate_schema_nullable.only(:not_null)
55
- end.to raise_error 'not_null is NOT NULL but has no presence validation'
191
+ expect do
192
+ is_expected.to validate_schema_nullable.only(*not_null_columns)
193
+ end.to raise_error do |error|
194
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
195
+ not_null_columns.sort.zip(error.message.split(', ')) do |column, message|
196
+ expect(message).to match /\A#{column} is NOT NULL but its presence validator was conditional: {:if=>\#<Proc:.*>}\z/
197
+ end
198
+ end
199
+ end
56
200
 
57
- expect do
58
- expect(Record).to validate_schema_nullable.only(:nullable_present)
59
- end.to raise_error 'nullable_present has unconditional presence validation but is missing NOT NULL'
201
+ specify 'unless: proc' do
202
+ validates :not_null, presence: true, unless: ->{ true }
60
203
 
61
- Record.instance_eval do
62
- clear_validators!
63
- validates :not_null_present, presence: true, on: :create
204
+ expect do
205
+ is_expected.to validate_schema_nullable.only(*not_null_columns)
206
+ end.to raise_error do |error|
207
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
208
+ not_null_columns.sort.zip(error.message.split(', ')) do |column, message|
209
+ expect(message).to match /\A#{column} is NOT NULL but its presence validator was conditional: {:unless=>\#<Proc:.*>}\z/
210
+ end
211
+ end
212
+ end
213
+
214
+ specify 'allow_nil: true' do
215
+ validates :not_null, presence: true, allow_nil: true
216
+
217
+ expect do
218
+ is_expected.to validate_schema_nullable.only(*not_null_columns)
219
+ end.to raise_error do |error|
220
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
221
+ not_null_columns.sort.zip(error.message.split(', ')) do |column, message|
222
+ expect(message).to eq "#{column} is NOT NULL but its presence validator was conditional: {:allow_nil=>true}"
223
+ end
224
+ end
225
+ end
226
+
227
+ specify 'allow_blank: true' do
228
+ validates :not_null, presence: true, allow_blank: true
229
+
230
+ expect do
231
+ is_expected.to validate_schema_nullable.only(*not_null_columns)
232
+ end.to raise_error do |error|
233
+ expect(error).to be_a RSpec::Expectations::ExpectationNotMetError
234
+ not_null_columns.sort.zip(error.message.split(', ')) do |column, message|
235
+ expect(message).to eq "#{column} is NOT NULL but its presence validator was conditional: {:allow_blank=>true}"
236
+ end
237
+ end
238
+ end
64
239
  end
65
- expect(Record).to_not validate_schema_nullable.only(:not_null_present)
66
- expect do
67
- expect(Record).to validate_schema_nullable.only(:not_null_present)
68
- end.to raise_error 'not_null_present is NOT NULL but its presence validator was conditional: {:on=>:create}'
69
-
70
- Record.instance_eval do
71
- clear_validators!
72
- validates :not_null_present, presence: true, if: ->{ false }
240
+ end
241
+
242
+ context 'called on class' do
243
+ include_examples 'Record' do
244
+ subject(:record) { Record }
73
245
  end
74
- expect(Record).to_not validate_schema_nullable.only(:not_null_present)
75
- expect do
76
- expect(Record).to validate_schema_nullable.only(:not_null_present)
77
- end.to raise_error /\Anot_null_present is NOT NULL but its presence validator was conditional: {:if=>\#<Proc:.*>}\z/
78
-
79
- Record.instance_eval do
80
- clear_validators!
81
- validates :not_null_present, presence: true, unless: ->{ true }
246
+ end
247
+
248
+ context 'called on instance' do
249
+ include_examples 'Record' do
250
+ subject(:record) { Record.new }
82
251
  end
83
- expect(Record).to_not validate_schema_nullable.only(:not_null_present)
84
- expect do
85
- expect(Record).to validate_schema_nullable.only(:not_null_present)
86
- end.to raise_error /\Anot_null_present is NOT NULL but its presence validator was conditional: {:unless=>\#<Proc:.*>}\z/
87
-
88
- Record.instance_eval do
89
- clear_validators!
90
- validates :not_null_present, presence: true, allow_nil: true
252
+ end
253
+
254
+ specify 'called on unrecognized object' do
255
+ expect { expect(double('object')).to validate_schema_nullable }.
256
+ to raise_error /#<RSpec::Mocks::Double:0x\h* @name="object"> does not inherit from ActiveRecord::Base/
257
+ end
258
+
259
+ context 'with belongs_to associations' do
260
+ include_examples 'Record' do
261
+ let(:not_null_columns) { [:not_null_id] }
262
+ let(:nullable_columns) { [:nullable_id] }
263
+
264
+ before do
265
+ create_table :other_records
266
+ stub_const('OtherRecord', Class.new(ActiveRecord::Base))
267
+
268
+ Record.instance_eval do
269
+ belongs_to :not_null, class_name: 'OtherRecord', foreign_key: :not_null_id
270
+ belongs_to :nullable, class_name: 'OtherRecord', foreign_key: :nullable_id
271
+ end
272
+ end
91
273
  end
92
- expect(Record).to_not validate_schema_nullable.only(:not_null_present)
93
- expect do
94
- expect(Record).to validate_schema_nullable.only(:not_null_present)
95
- end.to raise_error 'not_null_present is NOT NULL but its presence validator was conditional: {:allow_nil=>true}'
96
-
97
- Record.instance_eval do
98
- clear_validators!
99
- validates :not_null_present, presence: true, allow_blank: true
274
+ end
275
+
276
+ context 'with polymorphic belongs_to associations' do
277
+ include_examples 'Record' do
278
+ let(:not_null_columns) { [:not_null_id, :not_null_type] }
279
+ let(:nullable_columns) { [:nullable_id, :nullable_type] }
280
+
281
+ before do
282
+ Record.instance_eval do
283
+ belongs_to :not_null, polymorphic: true, foreign_key: :not_null_id, foreign_type: :not_null_type
284
+ belongs_to :nullable, polymorphic: true, foreign_key: :nullable_id, foreign_type: :nullable_type
285
+ end
286
+ end
100
287
  end
101
- expect(Record).to_not validate_schema_nullable.only(:not_null_present)
102
- expect do
103
- expect(Record).to validate_schema_nullable.only(:not_null_present)
104
- end.to raise_error 'not_null_present is NOT NULL but its presence validator was conditional: {:allow_blank=>true}'
105
288
  end
106
289
  end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+ require 'schema_expectations/util'
3
+
4
+ module SchemaExpectations
5
+ describe SchemaExpectations::Util do
6
+ specify '.slice_hash' do
7
+ expect(Util.slice_hash({}, :key)).to eq({})
8
+ expect(Util.slice_hash({ key: :value }, :key)).to eq(key: :value)
9
+ expect(Util.slice_hash({ other: :value }, :key)).to eq({})
10
+ expect(Util.slice_hash({
11
+ key_1: :value_1,
12
+ key_2: :value_2,
13
+ other: :value_3 }, :key_1, :key_2)).to eq(key_1: :value_1, key_2: :value_2)
14
+ end
15
+ end
16
+ end
data/spec/meta_spec.rb ADDED
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe SchemaExpectations do
4
+ specify 'Appraisals and .travis.yml are synced' do
5
+ env_bundle_gemfile = ENV['BUNDLE_GEMFILE']
6
+ ENV.delete 'BUNDLE_GEMFILE'
7
+
8
+ require 'appraisal'
9
+ require 'yaml'
10
+
11
+ travis_file = File.expand_path('../../.travis.yml', __FILE__)
12
+ travis_config = YAML.load(File.read(travis_file))
13
+
14
+ Appraisal::File.each do |appraisal|
15
+ error_message = "Appraisal #{appraisal.name} is out of sync. Run `rake refresh`"
16
+ expect(File.file?(appraisal.gemfile_path)).to be_truthy, error_message
17
+ expect(File.read(appraisal.gemfile_path)).to include(appraisal.gemfile.to_s), error_message
18
+ expect(travis_config['gemfile']).to be_a(Array), error_message
19
+ expect(travis_config['gemfile']).to include("gemfiles/#{File.basename(appraisal.gemfile_path)}"), error_message
20
+ end
21
+
22
+ ENV['BUNDLE_GEMFILE'] = env_bundle_gemfile
23
+ end
24
+
25
+ specify 'binding.pry is not committed' do
26
+ bindings = `git grep binding.pry`.split("\n")
27
+ bindings.reject! do |binding|
28
+ binding.start_with? 'spec/meta_spec.rb:'
29
+ end
30
+ expect(bindings).to be_empty
31
+ end
32
+
33
+ specify 'tests connect to postgresql', :active_record, :postgresql do
34
+ expect(ActiveRecord::Base.connection_config[:adapter]).to eq 'postgresql'
35
+ end
36
+
37
+ specify 'tests connect to sqlite3', :active_record, :sqlite3 do
38
+ expect(ActiveRecord::Base.connection_config[:adapter]).to eq 'sqlite3'
39
+ end
40
+
41
+ specify 'tests connect to mysql2', :active_record, :mysql2 do
42
+ expect(ActiveRecord::Base.connection_config[:adapter]).to eq 'mysql2'
43
+ end
44
+ end