dry-validation 0.6.0 → 0.7.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/.codeclimate.yml +1 -0
- data/.travis.yml +3 -2
- data/CHANGELOG.md +42 -0
- data/Gemfile +8 -1
- data/README.md +13 -89
- data/config/errors.yml +35 -29
- data/dry-validation.gemspec +2 -2
- data/examples/basic.rb +3 -7
- data/examples/each.rb +3 -8
- data/examples/form.rb +3 -6
- data/examples/nested.rb +7 -15
- data/lib/dry/validation.rb +33 -5
- data/lib/dry/validation/error.rb +10 -26
- data/lib/dry/validation/error_compiler.rb +69 -99
- data/lib/dry/validation/error_compiler/input.rb +148 -0
- data/lib/dry/validation/hint_compiler.rb +83 -33
- data/lib/dry/validation/input_processor_compiler.rb +98 -0
- data/lib/dry/validation/input_processor_compiler/form.rb +46 -0
- data/lib/dry/validation/input_processor_compiler/sanitizer.rb +46 -0
- data/lib/dry/validation/messages/abstract.rb +30 -10
- data/lib/dry/validation/messages/i18n.rb +2 -1
- data/lib/dry/validation/messages/namespaced.rb +1 -0
- data/lib/dry/validation/messages/yaml.rb +8 -5
- data/lib/dry/validation/result.rb +33 -25
- data/lib/dry/validation/schema.rb +168 -61
- data/lib/dry/validation/schema/attr.rb +5 -27
- data/lib/dry/validation/schema/check.rb +24 -0
- data/lib/dry/validation/schema/dsl.rb +97 -0
- data/lib/dry/validation/schema/form.rb +2 -26
- data/lib/dry/validation/schema/key.rb +32 -28
- data/lib/dry/validation/schema/rule.rb +88 -32
- data/lib/dry/validation/schema/value.rb +77 -27
- data/lib/dry/validation/schema_compiler.rb +38 -0
- data/lib/dry/validation/version.rb +1 -1
- data/spec/fixtures/locales/pl.yml +1 -1
- data/spec/integration/attr_spec.rb +122 -0
- data/spec/integration/custom_error_messages_spec.rb +9 -11
- data/spec/integration/custom_predicates_spec.rb +68 -18
- data/spec/integration/error_compiler_spec.rb +259 -65
- data/spec/integration/hints_spec.rb +28 -9
- data/spec/integration/injecting_rules_spec.rb +11 -12
- data/spec/integration/localized_error_messages_spec.rb +16 -16
- data/spec/integration/messages/i18n_spec.rb +9 -5
- data/spec/integration/optional_keys_spec.rb +9 -11
- data/spec/integration/schema/array_schema_spec.rb +23 -0
- data/spec/integration/schema/check_rules_spec.rb +39 -31
- data/spec/integration/schema/check_with_nth_el_spec.rb +25 -0
- data/spec/integration/schema/each_with_set_spec.rb +23 -24
- data/spec/integration/schema/form_spec.rb +122 -0
- data/spec/integration/schema/inheriting_schema_spec.rb +31 -0
- data/spec/integration/schema/input_processor_spec.rb +46 -0
- data/spec/integration/schema/macros/confirmation_spec.rb +33 -0
- data/spec/integration/schema/macros/maybe_spec.rb +32 -0
- data/spec/integration/schema/macros/required_spec.rb +59 -0
- data/spec/integration/schema/macros/when_spec.rb +65 -0
- data/spec/integration/schema/nested_values_spec.rb +41 -0
- data/spec/integration/schema/not_spec.rb +14 -14
- data/spec/integration/schema/option_with_default_spec.rb +30 -0
- data/spec/integration/schema/reusing_schema_spec.rb +33 -0
- data/spec/integration/schema/using_types_spec.rb +29 -0
- data/spec/integration/schema/xor_spec.rb +17 -14
- data/spec/integration/schema_spec.rb +75 -245
- data/spec/shared/rule_compiler.rb +8 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit/hint_compiler_spec.rb +10 -10
- data/spec/unit/{input_type_compiler_spec.rb → input_processor_compiler/form_spec.rb} +88 -73
- data/spec/unit/schema/key_spec.rb +33 -0
- data/spec/unit/schema/rule_spec.rb +7 -6
- data/spec/unit/schema/value_spec.rb +187 -54
- metadata +53 -31
- data/.rubocop.yml +0 -16
- data/.rubocop_todo.yml +0 -7
- data/lib/dry/validation/input_type_compiler.rb +0 -83
- data/lib/dry/validation/schema/definition.rb +0 -74
- data/lib/dry/validation/schema/result.rb +0 -68
- data/rakelib/rubocop.rake +0 -18
- data/spec/integration/rule_groups_spec.rb +0 -94
- data/spec/integration/schema/attrs_spec.rb +0 -38
- data/spec/integration/schema/default_key_behavior_spec.rb +0 -23
- data/spec/integration/schema/grouped_rules_spec.rb +0 -57
- data/spec/integration/schema/nested_spec.rb +0 -31
- data/spec/integration/schema_form_spec.rb +0 -97
@@ -0,0 +1,122 @@
|
|
1
|
+
RSpec.describe Dry::Validation::Schema::Form, 'defining a schema' do
|
2
|
+
subject(:schema) do
|
3
|
+
Dry::Validation.Form do
|
4
|
+
key(:email).required
|
5
|
+
|
6
|
+
key(:age).maybe(:int?, gt?: 18)
|
7
|
+
|
8
|
+
key(:address).schema do
|
9
|
+
key(:city).required
|
10
|
+
key(:street).required
|
11
|
+
|
12
|
+
key(:loc) do
|
13
|
+
hash? do
|
14
|
+
key(:lat).required(:float?)
|
15
|
+
key(:lng).required(:float?)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
optional(:password).maybe.confirmation
|
21
|
+
|
22
|
+
optional(:phone_number).maybe(:int?, gt?: 0)
|
23
|
+
|
24
|
+
rule(:email_valid) { value(:email).email? }
|
25
|
+
|
26
|
+
configure do
|
27
|
+
def email?(value)
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#messages' do
|
35
|
+
it 'returns compiled error messages' do
|
36
|
+
result = schema.('email' => '', 'age' => '19')
|
37
|
+
|
38
|
+
expect(result.messages).to eql(
|
39
|
+
email: ['must be filled'],
|
40
|
+
address: ['is missing']
|
41
|
+
)
|
42
|
+
|
43
|
+
expect(result.output).to eql(email: '', age: 19)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'returns hints for nested data' do
|
47
|
+
result = schema.(
|
48
|
+
'email' => 'jane@doe.org',
|
49
|
+
'age' => '19',
|
50
|
+
'address' => {
|
51
|
+
'city' => '',
|
52
|
+
'street' => 'Street 1/2',
|
53
|
+
'loc' => { 'lat' => '123.456', 'lng' => '' }
|
54
|
+
}
|
55
|
+
)
|
56
|
+
|
57
|
+
expect(result.messages).to eql(
|
58
|
+
address: {
|
59
|
+
loc: { lng: ['must be filled'] },
|
60
|
+
city: ['must be filled']
|
61
|
+
}
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
describe '#call' do
|
67
|
+
it 'passes when attributes are valid' do
|
68
|
+
result = schema.(
|
69
|
+
'email' => 'jane@doe.org',
|
70
|
+
'age' => '19',
|
71
|
+
'address' => {
|
72
|
+
'city' => 'NYC',
|
73
|
+
'street' => 'Street 1/2',
|
74
|
+
'loc' => { 'lat' => '123.456', 'lng' => '456.123' }
|
75
|
+
}
|
76
|
+
)
|
77
|
+
|
78
|
+
expect(result).to be_success
|
79
|
+
|
80
|
+
expect(result.output).to eql(
|
81
|
+
email: 'jane@doe.org', age: 19,
|
82
|
+
address: {
|
83
|
+
city: 'NYC', street: 'Street 1/2',
|
84
|
+
loc: { lat: 123.456, lng: 456.123 }
|
85
|
+
}
|
86
|
+
)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'validates presence of an email and min age value' do
|
90
|
+
result = schema.('email' => '', 'age' => '18')
|
91
|
+
|
92
|
+
expect(result.messages).to eql(
|
93
|
+
address: ['is missing'],
|
94
|
+
age: ['must be greater than 18'],
|
95
|
+
email: ['must be filled']
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'handles optionals' do
|
100
|
+
result = schema.(
|
101
|
+
'email' => 'jane@doe.org',
|
102
|
+
'age' => '19',
|
103
|
+
'phone_number' => '12',
|
104
|
+
'address' => {
|
105
|
+
'city' => 'NYC',
|
106
|
+
'street' => 'Street 1/2',
|
107
|
+
'loc' => { 'lat' => '123.456', 'lng' => '456.123' }
|
108
|
+
}
|
109
|
+
)
|
110
|
+
|
111
|
+
expect(result).to be_success
|
112
|
+
|
113
|
+
expect(result.output).to eql(
|
114
|
+
email: 'jane@doe.org', age: 19, phone_number: 12,
|
115
|
+
address: {
|
116
|
+
city: 'NYC', street: 'Street 1/2',
|
117
|
+
loc: { lat: 123.456, lng: 456.123 }
|
118
|
+
}
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
RSpec.describe 'Inheriting schema' do
|
2
|
+
subject(:schema) do
|
3
|
+
Dry::Validation.Schema(base_schema) do
|
4
|
+
key(:location).schema do
|
5
|
+
key(:lat).required(:float?)
|
6
|
+
key(:lng).required(:float?)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:base_schema) do
|
12
|
+
Dry::Validation.Schema do
|
13
|
+
key(:city).required
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'inherits rules from parent schema' do
|
18
|
+
expect(schema.(city: 'NYC', location: { lat: 1.23, lng: 45.6 })).to be_success
|
19
|
+
|
20
|
+
expect(schema.(city: '', location: { lat: 1.23, lng: 45.6 }).messages).to eql(
|
21
|
+
city: ['must be filled']
|
22
|
+
)
|
23
|
+
|
24
|
+
expect(schema.(city: 'NYC', location: { lat: nil, lng: '45.6' }).messages).to eql(
|
25
|
+
location: {
|
26
|
+
lat: ['must be filled'],
|
27
|
+
lng: ['must be a float']
|
28
|
+
}
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
RSpec.describe Dry::Validation::Schema, 'setting input processor in schema' do
|
2
|
+
subject(:schema) do
|
3
|
+
Dry::Validation.Schema do
|
4
|
+
configure do
|
5
|
+
config.input_processor = :sanitizer
|
6
|
+
end
|
7
|
+
|
8
|
+
key(:email).required
|
9
|
+
|
10
|
+
key(:age).maybe(:int?, gt?: 18)
|
11
|
+
|
12
|
+
key(:address).schema do
|
13
|
+
key(:city).required
|
14
|
+
key(:street).required
|
15
|
+
end
|
16
|
+
|
17
|
+
key(:phone_numbers).each do
|
18
|
+
key(:prefix).required
|
19
|
+
key(:value).required
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'rejects unspecified keys' do
|
25
|
+
result = schema.(
|
26
|
+
email: 'jane@doe',
|
27
|
+
age: 19,
|
28
|
+
such: 'key',
|
29
|
+
address: { city: 'NYC', street: 'Street', wow: 'bad' },
|
30
|
+
phone_numbers: [
|
31
|
+
{ prefix: '48', value: '123' },
|
32
|
+
{ lol: '!!', prefix: '1', value: '312' }
|
33
|
+
]
|
34
|
+
)
|
35
|
+
|
36
|
+
expect(result.output).to eql(
|
37
|
+
email: 'jane@doe',
|
38
|
+
age: 19,
|
39
|
+
address: { city: 'NYC', street: 'Street' },
|
40
|
+
phone_numbers: [
|
41
|
+
{ prefix: '48', value: '123' },
|
42
|
+
{ prefix: '1', value: '312' }
|
43
|
+
]
|
44
|
+
)
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
RSpec.describe 'Macros #confirmation' do
|
2
|
+
describe 'with a maybe password with min-size specified' do
|
3
|
+
subject(:schema) do
|
4
|
+
Dry::Validation.Schema do
|
5
|
+
configure do
|
6
|
+
def self.messages
|
7
|
+
Messages.default.merge(
|
8
|
+
en: { errors: { password_confirmation: 'does not match' } }
|
9
|
+
)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
key(:password).maybe(min_size?: 3).confirmation
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'passes when values are equal' do
|
18
|
+
expect(schema.(password: 'foo', password_confirmation: 'foo')).to be_success
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'fails when source value is invalid' do
|
22
|
+
expect(schema.(password: 'fo', password_confirmation: '').messages).to eql(
|
23
|
+
password: ['size cannot be less than 3']
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'fails when values are not equal' do
|
28
|
+
expect(schema.(password: 'foo', password_confirmation: 'fo').messages).to eql(
|
29
|
+
password_confirmation: ['does not match']
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
RSpec.describe 'Macros #maybe' do
|
2
|
+
describe 'with no args' do
|
3
|
+
subject(:schema) do
|
4
|
+
Dry::Validation.Schema do
|
5
|
+
key(:email).maybe
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'generates none? | filled? rule' do
|
10
|
+
expect(schema.(email: nil).messages).to be_empty
|
11
|
+
expect(schema.(email: 'jane@doe').messages).to be_empty
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'with a predicate with args' do
|
16
|
+
subject(:schema) do
|
17
|
+
Dry::Validation.Schema do
|
18
|
+
key(:name).maybe(min_size?: 3)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'generates none? | (filled? & min_size?) rule' do
|
23
|
+
expect(schema.(name: nil).messages).to be_empty
|
24
|
+
|
25
|
+
expect(schema.(name: 'jane').messages).to be_empty
|
26
|
+
|
27
|
+
expect(schema.(name: 'xy').messages).to eql(
|
28
|
+
name: ['size cannot be less than 3']
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
RSpec.describe 'Macros #required' do
|
2
|
+
describe 'with no args' do
|
3
|
+
subject(:schema) do
|
4
|
+
Dry::Validation.Schema do
|
5
|
+
key(:email).required
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'generates filled? rule' do
|
10
|
+
expect(schema.(email: '').messages).to eql(
|
11
|
+
email: ['must be filled']
|
12
|
+
)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe 'with a type specification' do
|
17
|
+
subject(:schema) do
|
18
|
+
Dry::Validation.Schema do
|
19
|
+
key(:age).required(:int?)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'generates filled? & int? rule' do
|
24
|
+
expect(schema.(age: nil).messages).to eql(
|
25
|
+
age: ['must be filled']
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'with a predicate with args' do
|
31
|
+
context 'with a flat arg' do
|
32
|
+
subject(:schema) do
|
33
|
+
Dry::Validation.Schema do
|
34
|
+
key(:age).required(:int?, gt?: 18)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'generates filled? & int? & gt? rule' do
|
39
|
+
expect(schema.(age: nil).messages).to eql(
|
40
|
+
age: ['must be filled', 'must be greater than 18']
|
41
|
+
)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with a range arg' do
|
46
|
+
subject(:schema) do
|
47
|
+
Dry::Validation.Schema do
|
48
|
+
key(:age).required(:int?, size?: 18..24)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'generates filled? & int? & gt? rule' do
|
53
|
+
expect(schema.(age: nil).messages).to eql(
|
54
|
+
age: ['must be filled', 'size must be within 18 - 24']
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
RSpec.describe 'Macros #when' do
|
2
|
+
context 'with a result rule returned from the block' do
|
3
|
+
subject(:schema) do
|
4
|
+
Dry::Validation.Schema do
|
5
|
+
key(:email).maybe
|
6
|
+
|
7
|
+
key(:login).required.when(:true?) do
|
8
|
+
value(:email).filled?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'generates check rule' do
|
14
|
+
expect(schema.(login: true, email: nil).messages).to eql(
|
15
|
+
email: ['must be filled']
|
16
|
+
)
|
17
|
+
|
18
|
+
expect(schema.(login: false, email: nil).messages).to be_empty
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe 'with a result rule depending on another result' do
|
23
|
+
subject(:schema) do
|
24
|
+
Dry::Validation.Schema do
|
25
|
+
key(:left).maybe(:int?)
|
26
|
+
key(:right).maybe(:int?)
|
27
|
+
|
28
|
+
key(:compare).maybe(:bool?).when(:true?) do
|
29
|
+
value(:left).gt?(value(:right))
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'generates check rule' do
|
35
|
+
expect(schema.(compare: false, left: nil, right: nil)).to be_success
|
36
|
+
|
37
|
+
expect(schema.(compare: true, left: 1, right: 2).messages).to eql(
|
38
|
+
left: ['must be greater than 2']
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe 'with multiple result rules' do
|
44
|
+
subject(:schema) do
|
45
|
+
Dry::Validation.Schema do
|
46
|
+
key(:email).maybe
|
47
|
+
key(:password).maybe
|
48
|
+
|
49
|
+
key(:login).maybe(:bool?).when(:true?) do
|
50
|
+
value(:email).filled?
|
51
|
+
value(:password).filled?
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'generates check rule' do
|
57
|
+
expect(schema.(login: false, email: nil, password: nil)).to be_success
|
58
|
+
|
59
|
+
expect(schema.(login: true, email: nil, password: nil).messages).to eql(
|
60
|
+
email: ['must be filled'],
|
61
|
+
password: ['must be filled']
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
RSpec.describe Schema, 'using nested values' do
|
2
|
+
let(:schema) do
|
3
|
+
Dry::Validation.Schema do
|
4
|
+
key(:email).maybe
|
5
|
+
|
6
|
+
key(:settings) do
|
7
|
+
optional(:offers).required(:bool?).when(:true?) do
|
8
|
+
value([:settings, :newsletter]).false?
|
9
|
+
end
|
10
|
+
|
11
|
+
key(:newsletter).required(:bool?).when(:true?) do
|
12
|
+
value(:email).filled?
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'passes when newsletter setting is false' do
|
19
|
+
expect(schema.(settings: { newsletter: false }, email: nil)).to be_success
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'passes when newsletter setting is true and email is filled' do
|
23
|
+
expect(schema.(settings: { newsletter: false }, email: 'jane@doe')).to be_success
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'passes when offers is false and newsletter is true' do
|
27
|
+
expect(schema.(settings: { offers: false, newsletter: true }, email: 'jane@doe')).to be_success
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'fails when newsletter is true and email is not filled' do
|
31
|
+
expect(schema.(settings: { newsletter: true }, email: nil).messages).to eql(
|
32
|
+
email: ['must be filled']
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'fails when offers is true and newsletter is true' do
|
37
|
+
expect(schema.(settings: { offers: true, newsletter: true }, email: 'jane@doe').messages).to eql(
|
38
|
+
settings: { newsletter: ['must be false'] }
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
@@ -1,34 +1,34 @@
|
|
1
1
|
RSpec.describe 'Schema with negated rules' do
|
2
|
-
subject(:
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
2
|
+
subject(:schema) do
|
3
|
+
Dry::Validation.Schema do
|
4
|
+
configure do
|
5
|
+
def self.messages
|
6
|
+
Messages.default.merge(
|
7
|
+
en: { errors: { be_reasonable: 'you cannot eat cake and have cake!' } }
|
8
|
+
)
|
9
|
+
end
|
10
10
|
end
|
11
11
|
|
12
|
-
optional(:eat_cake)
|
13
|
-
optional(:have_cake)
|
12
|
+
optional(:eat_cake).required
|
13
|
+
optional(:have_cake).required
|
14
14
|
|
15
15
|
rule(:be_reasonable) do
|
16
|
-
|
16
|
+
value(:eat_cake).eql?('yes!') & value(:have_cake).eql?('yes!').not
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
21
|
describe '#messages' do
|
22
22
|
it 'passes when only one option is selected' do
|
23
|
-
messages =
|
23
|
+
messages = schema.(eat_cake: 'yes!', have_cake: 'no!').messages[:be_reasonable]
|
24
24
|
|
25
25
|
expect(messages).to be(nil)
|
26
26
|
end
|
27
27
|
|
28
28
|
it 'fails when both options are selected' do
|
29
|
-
messages =
|
29
|
+
messages = schema.(eat_cake: 'yes!', have_cake: 'yes!').messages[:be_reasonable]
|
30
30
|
|
31
|
-
expect(messages).to eql([
|
31
|
+
expect(messages).to eql(['you cannot eat cake and have cake!'])
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|