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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +19 -5
- data/Appraisals +33 -0
- data/CHANGELOG.md +17 -1
- data/README.md +4 -0
- data/Rakefile +12 -0
- data/gemfiles/activerecord_3.1.gemfile +7 -0
- data/gemfiles/activerecord_3.2.gemfile +7 -0
- data/gemfiles/activerecord_4.0.gemfile +7 -0
- data/gemfiles/activerecord_4.1.gemfile +7 -0
- data/gemfiles/activerecord_4.2.gemfile +7 -0
- data/gemfiles/default.gemfile +5 -0
- data/gemfiles/rspec_3.0.gemfile +7 -0
- data/gemfiles/rspec_3.1.gemfile +7 -0
- data/gemfiles/rspec_3.2.gemfile +7 -0
- data/lib/schema_expectations/active_record/column_reflector.rb +92 -0
- data/lib/schema_expectations/active_record/validation_reflector.rb +59 -0
- data/lib/schema_expectations/config.rb +25 -0
- data/lib/schema_expectations/rspec_matchers/validate_schema_nullable.rb +49 -58
- data/lib/schema_expectations/util.rb +9 -0
- data/lib/schema_expectations/version.rb +1 -1
- data/lib/schema_expectations.rb +1 -0
- data/schema_expectations.gemspec +12 -3
- data/spec/db/database.yml +13 -0
- data/spec/lib/schema_expectations/active_record/column_reflector_spec.rb +150 -0
- data/spec/lib/schema_expectations/active_record/validation_reflector_spec.rb +62 -0
- data/spec/lib/schema_expectations/config_spec.rb +22 -0
- data/spec/lib/schema_expectations/rspec_matchers/validate_schema_nullable_spec.rb +260 -77
- data/spec/lib/schema_expectations/util_spec.rb +16 -0
- data/spec/meta_spec.rb +44 -0
- data/spec/spec_helper.rb +11 -2
- data/spec/support/active_record.rb +34 -12
- data/spec/support/active_record_helpers.rb +51 -0
- data/spec/support/gem_filters.rb +6 -0
- 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 :
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
11
|
+
let(:not_null_columns) { [:not_null] }
|
12
|
+
let(:nullable_columns) { [:nullable] }
|
13
|
+
let(:columns) { not_null_columns + nullable_columns }
|
11
14
|
|
12
|
-
|
13
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
58
|
+
context 'with not_null present' do
|
59
|
+
before { validates :not_null, presence: true }
|
26
60
|
|
27
|
-
|
28
|
-
|
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
|
-
|
31
|
-
|
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
|
-
|
36
|
-
t.string :nullable_present
|
162
|
+
is_expected.to validate_schema_nullable
|
37
163
|
end
|
38
164
|
|
39
|
-
|
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
|
-
|
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
|
-
|
47
|
-
|
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
|
-
|
50
|
-
|
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
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|