attributor 5.0.2 → 5.1.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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -0
  3. data/.travis.yml +6 -4
  4. data/CHANGELOG.md +6 -1
  5. data/Gemfile +1 -1
  6. data/Guardfile +14 -8
  7. data/Rakefile +4 -5
  8. data/attributor.gemspec +34 -29
  9. data/lib/attributor.rb +23 -29
  10. data/lib/attributor/attribute.rb +108 -127
  11. data/lib/attributor/attribute_resolver.rb +12 -26
  12. data/lib/attributor/dsl_compiler.rb +17 -21
  13. data/lib/attributor/dumpable.rb +1 -2
  14. data/lib/attributor/example_mixin.rb +5 -8
  15. data/lib/attributor/exceptions.rb +5 -6
  16. data/lib/attributor/extensions/randexp.rb +3 -5
  17. data/lib/attributor/extras/field_selector.rb +4 -4
  18. data/lib/attributor/extras/field_selector/transformer.rb +6 -7
  19. data/lib/attributor/families/numeric.rb +0 -2
  20. data/lib/attributor/families/temporal.rb +1 -4
  21. data/lib/attributor/hash_dsl_compiler.rb +22 -25
  22. data/lib/attributor/type.rb +24 -32
  23. data/lib/attributor/types/bigdecimal.rb +7 -14
  24. data/lib/attributor/types/boolean.rb +5 -8
  25. data/lib/attributor/types/class.rb +9 -10
  26. data/lib/attributor/types/collection.rb +34 -44
  27. data/lib/attributor/types/container.rb +9 -15
  28. data/lib/attributor/types/csv.rb +7 -10
  29. data/lib/attributor/types/date.rb +20 -25
  30. data/lib/attributor/types/date_time.rb +7 -14
  31. data/lib/attributor/types/float.rb +4 -6
  32. data/lib/attributor/types/hash.rb +171 -196
  33. data/lib/attributor/types/ids.rb +2 -6
  34. data/lib/attributor/types/integer.rb +12 -17
  35. data/lib/attributor/types/model.rb +39 -48
  36. data/lib/attributor/types/object.rb +2 -4
  37. data/lib/attributor/types/polymorphic.rb +118 -0
  38. data/lib/attributor/types/regexp.rb +4 -5
  39. data/lib/attributor/types/string.rb +6 -7
  40. data/lib/attributor/types/struct.rb +8 -15
  41. data/lib/attributor/types/symbol.rb +3 -6
  42. data/lib/attributor/types/tempfile.rb +5 -6
  43. data/lib/attributor/types/time.rb +11 -11
  44. data/lib/attributor/types/uri.rb +9 -10
  45. data/lib/attributor/version.rb +1 -1
  46. data/spec/attribute_resolver_spec.rb +57 -78
  47. data/spec/attribute_spec.rb +174 -216
  48. data/spec/attributor_spec.rb +11 -15
  49. data/spec/dsl_compiler_spec.rb +19 -33
  50. data/spec/dumpable_spec.rb +6 -7
  51. data/spec/extras/field_selector/field_selector_spec.rb +1 -1
  52. data/spec/families_spec.rb +1 -3
  53. data/spec/hash_dsl_compiler_spec.rb +65 -74
  54. data/spec/spec_helper.rb +9 -3
  55. data/spec/support/hashes.rb +2 -3
  56. data/spec/support/models.rb +30 -36
  57. data/spec/support/polymorphics.rb +10 -0
  58. data/spec/type_spec.rb +38 -61
  59. data/spec/types/bigdecimal_spec.rb +11 -15
  60. data/spec/types/boolean_spec.rb +12 -39
  61. data/spec/types/class_spec.rb +10 -11
  62. data/spec/types/collection_spec.rb +72 -81
  63. data/spec/types/container_spec.rb +22 -26
  64. data/spec/types/csv_spec.rb +15 -16
  65. data/spec/types/date_spec.rb +16 -33
  66. data/spec/types/date_time_spec.rb +16 -33
  67. data/spec/types/file_upload_spec.rb +1 -2
  68. data/spec/types/float_spec.rb +7 -14
  69. data/spec/types/hash_spec.rb +285 -289
  70. data/spec/types/ids_spec.rb +5 -7
  71. data/spec/types/integer_spec.rb +37 -46
  72. data/spec/types/model_spec.rb +111 -128
  73. data/spec/types/polymorphic_spec.rb +134 -0
  74. data/spec/types/regexp_spec.rb +4 -7
  75. data/spec/types/string_spec.rb +17 -21
  76. data/spec/types/struct_spec.rb +40 -47
  77. data/spec/types/tempfile_spec.rb +1 -2
  78. data/spec/types/temporal_spec.rb +9 -0
  79. data/spec/types/time_spec.rb +16 -32
  80. data/spec/types/type_spec.rb +15 -0
  81. data/spec/types/uri_spec.rb +6 -7
  82. metadata +77 -25
@@ -1,74 +1,69 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
2
 
3
-
4
3
  describe Attributor::Attribute do
5
-
6
4
  let(:attribute_options) { Hash.new }
7
5
  let(:type) { Attributor::String }
8
6
 
9
7
  subject(:attribute) { Attributor::Attribute.new(type, attribute_options) }
10
8
 
11
- let(:context) { ["context"] }
12
- let(:value) { "one" }
9
+ let(:context) { ['context'] }
10
+ let(:value) { 'one' }
13
11
 
14
12
  context 'initialize' do
15
13
  its(:type) { should be type }
16
14
  its(:options) { should be attribute_options }
17
15
 
18
16
  it 'calls check_options!' do
19
- Attributor::Attribute.any_instance.should_receive(:check_options!)
17
+ expect_any_instance_of(Attributor::Attribute).to receive(:check_options!)
20
18
  Attributor::Attribute.new(type, attribute_options)
21
19
  end
22
20
 
23
21
  context 'for anonymous types (aka Structs)' do
24
22
  before do
25
- Attributor.should_receive(:resolve_type).once.with(Struct,attribute_options, anything()).and_call_original
23
+ expect(Attributor).to receive(:resolve_type).once.with(Struct, attribute_options, anything).and_call_original
26
24
  end
27
25
 
28
26
  it 'generates the class' do
29
- thing = Attributor::Attribute.new(Struct, attribute_options) do
27
+ Attributor::Attribute.new(Struct, attribute_options) do
30
28
  attribute :id, Integer
31
29
  end
32
30
  end
33
-
34
31
  end
35
-
36
32
  end
37
33
 
38
34
  context '==' do
39
35
  let(:other_attribute) { Attributor::Attribute.new(type, attribute_options) }
40
- it { should == other_attribute}
36
+ it { should eq other_attribute }
41
37
  end
42
38
 
43
39
  context 'describe' do
44
- let(:attribute_options) { {:required => true, :values => ["one"], :description => "something", :min => 0} }
40
+ let(:attribute_options) { { required: true, values: ['one'], description: 'something', min: 0 } }
45
41
  let(:expected) do
46
- h = {type: {name: 'String', id: type.id, family: type.family}}
47
- common = attribute_options.select{|k,v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k }
48
- h.merge!( common )
49
- h[:options] = {:min => 0 }
42
+ h = { type: { name: 'String', id: type.id, family: type.family } }
43
+ common = attribute_options.select { |k, _v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k }
44
+ h.merge!(common)
45
+ h[:options] = { min: 0 }
50
46
  h
51
47
  end
52
48
 
53
-
54
- its(:describe) { should == expected }
49
+ its(:describe) { should eq expected }
55
50
 
56
51
  context 'with example options' do
57
- let(:attribute_options) { {:description=> "something", :example => "ex_def"} }
52
+ let(:attribute_options) { { description: 'something', example: 'ex_def' } }
58
53
  its(:describe) { should have_key(:example_definition) }
59
54
  its(:describe) { should_not have_key(:example) }
60
55
  it 'should have the example value in the :example_definition key' do
61
- subject.describe[:example_definition].should == "ex_def"
56
+ expect(subject.describe[:example_definition]).to eq 'ex_def'
62
57
  end
63
58
  end
64
59
 
65
60
  context 'with custom_data' do
66
- let(:custom_data) { {loggable: true, visible_in_ui: false} }
67
- let(:attribute_options) { {custom_data: custom_data} }
61
+ let(:custom_data) { { loggable: true, visible_in_ui: false } }
62
+ let(:attribute_options) { { custom_data: custom_data } }
68
63
  its(:describe) { should have_key(:custom_data) }
69
64
 
70
65
  it 'keep the custom data attribute' do
71
- subject.describe[:custom_data].should == custom_data
66
+ expect(subject.describe[:custom_data]).to eq custom_data
72
67
  end
73
68
  end
74
69
 
@@ -80,99 +75,92 @@ describe Attributor::Attribute do
80
75
  end
81
76
  end
82
77
 
83
-
84
78
  subject(:description) { attribute.describe }
85
79
 
86
-
87
80
  it 'uses the name of the first non-anonymous ancestor' do
88
- description[:type][:name].should == 'Struct'
81
+ expect(description[:type][:name]).to eq 'Struct'
89
82
  end
90
83
 
91
84
  it 'includes sub-attributes' do
92
- description[:type][:attributes].should have_key(:id)
85
+ expect(description[:type][:attributes]).to have_key(:id)
93
86
  end
94
-
95
87
  end
96
88
 
97
89
  context 'with an example' do
98
-
99
- let(:attribute_options){ {} }
100
- let(:example){ attribute.example }
101
- subject(:described){ attribute.describe(false, example: example) }
90
+ let(:attribute_options) { {} }
91
+ let(:example) { attribute.example }
92
+ subject(:described) { attribute.describe(false, example: example) }
102
93
 
103
94
  context 'using a simple terminal type' do
104
95
  let(:type) { String }
105
- its(:keys){ should include(:example) }
96
+ its(:keys) { should include(:example) }
106
97
  it 'should have the passed example value' do
107
- described.should have_key(:example)
108
- described[:example].should eq(example)
98
+ expect(described).to have_key(:example)
99
+ expect(described[:example]).to eq(example)
109
100
  end
110
101
  it 'should have removed the example from the :type' do
111
- described[:type].should_not have_key(:example)
102
+ expect(described[:type]).not_to have_key(:example)
112
103
  end
113
-
114
104
  end
115
105
 
116
106
  context 'using a complex type' do
117
107
  let(:type) { Cormorant }
118
- its(:keys){ should_not include(:example) }
108
+ its(:keys) { should_not include(:example) }
119
109
 
120
110
  it 'Should see examples in the right places, depending on leaf/no-leaf types' do
121
111
  # String, a leaf attribute type: should have example
122
112
  name_attr = described[:type][:attributes][:name]
123
- name_attr.should include(:example)
124
- name_attr[:type].should_not include(:example)
113
+ expect(name_attr).to include(:example)
114
+ expect(name_attr[:type]).not_to include(:example)
125
115
 
126
116
  # Struct, a non-leaf attribute type: shouldn't have example
127
- ts_attr = described[:type][:attributes][:timestamps]
128
- ts_attr.should_not include(:example)
129
- ts_attr[:type].should_not include(:example)
117
+ ts_attr = described[:type][:attributes][:timestamps]
118
+ expect(ts_attr).not_to include(:example)
119
+ expect(ts_attr[:type]).not_to include(:example)
130
120
 
131
121
  # DateTime inside a Struct, a nested leaf attribute type: should have example
132
- born_attr = ts_attr[:type][:attributes][:born_at]
133
- born_attr.should include(:example)
134
- born_attr[:type].should_not include(:example)
122
+ born_attr = ts_attr[:type][:attributes][:born_at]
123
+ expect(born_attr).to include(:example)
124
+ expect(born_attr[:type]).not_to include(:example)
135
125
  end
136
126
  end
137
127
  end
138
128
  end
139
129
 
140
-
141
130
  context 'parse' do
142
- let(:loaded_object){ double("I'm loaded") }
131
+ let(:loaded_object) { double("I'm loaded") }
143
132
  it 'loads and validates' do
144
- attribute.should_receive(:load).with(value,Attributor::DEFAULT_ROOT_CONTEXT).and_return(loaded_object)
145
- attribute.should_receive(:validate).with(loaded_object,Attributor::DEFAULT_ROOT_CONTEXT).and_call_original
133
+ expect(attribute).to receive(:load).with(value, Attributor::DEFAULT_ROOT_CONTEXT).and_return(loaded_object)
134
+ expect(attribute).to receive(:validate).with(loaded_object, Attributor::DEFAULT_ROOT_CONTEXT).and_call_original
146
135
 
147
136
  attribute.parse(value)
148
137
  end
149
138
  end
150
139
 
151
-
152
140
  context 'checking options' do
153
141
  it 'raises for invalid options' do
154
- expect {
142
+ expect do
155
143
  Attributor::Attribute.new(Integer, unknown_opt: true)
156
- }.to raise_error(/unsupported option/)
144
+ end.to raise_error(/unsupported option/)
157
145
  end
158
146
 
159
147
  it 'has a spec that we try to validate the :default value' do
160
- expect {
161
- Attributor::Attribute.new(Integer, default: "not an okay integer")
162
- }.to raise_error(/Default value doesn't have the correct attribute type/)
148
+ expect do
149
+ Attributor::Attribute.new(Integer, default: 'not an okay integer')
150
+ end.to raise_error(/Default value doesn't have the correct attribute type/)
163
151
  end
164
152
 
165
153
  context 'custom_data' do
166
154
  it 'raises when not a hash' do
167
- expect {
155
+ expect do
168
156
  Attributor::Attribute.new(Integer, custom_data: 1)
169
- }.to raise_error(/custom_data must be a Hash/)
157
+ end.to raise_error(/custom_data must be a Hash/)
170
158
  end
171
159
 
172
160
  it 'does not raise for hashes' do
173
- expect {
174
- Attributor::Attribute.new(Integer, custom_data: {loggable: true})
175
- }.not_to raise_error
161
+ expect do
162
+ Attributor::Attribute.new(Integer, custom_data: { loggable: true })
163
+ end.not_to raise_error
176
164
  end
177
165
  end
178
166
  end
@@ -183,54 +171,53 @@ describe Attributor::Attribute do
183
171
  context 'with nothing specified' do
184
172
  let(:attribute_options) { {} }
185
173
  before do
186
- type.should_receive(:example).and_return(example)
174
+ expect(type).to receive(:example).and_return(example)
187
175
  end
188
176
 
189
177
  it 'defers to the type' do
190
- attribute.example.should be example
178
+ expect(attribute.example).to be example
191
179
  end
192
180
  end
193
181
 
194
182
  context 'with an attribute that has the values option set' do
195
- let(:values) { ["one", "two"] }
196
- let(:attribute_options) { {:values => values} }
183
+ let(:values) { %w(one two) }
184
+ let(:attribute_options) { { values: values } }
197
185
  it 'picks a random value' do
198
- values.should include subject.example
186
+ expect(values).to include subject.example
199
187
  end
200
-
201
188
  end
202
189
 
203
190
  context 'deterministic examples' do
204
191
  let(:example) { /\w+/ }
205
- let(:attribute_options) { {:example => example} }
192
+ let(:attribute_options) { { example: example } }
206
193
 
207
194
  it 'can take a context to pre-seed the random number generator' do
208
195
  example_1 = subject.example(['context'])
209
196
  example_2 = subject.example(['context'])
210
197
 
211
- example_1.should eq example_2
198
+ expect(example_1).to eq example_2
212
199
  end
213
200
 
214
201
  it 'can take a context to pre-seed the random number generator' do
215
202
  example_1 = subject.example(['context'])
216
203
  example_2 = subject.example(['different context'])
217
204
 
218
- example_1.should_not eq example_2
205
+ expect(example_1).not_to eq example_2
219
206
  end
220
207
  end
221
208
 
222
209
  context 'with an example option' do
223
- let(:example){ "Bob" }
224
- let(:attribute_options) { {example: example , regexp: /Bob/ } }
210
+ let(:example) { 'Bob' }
211
+ let(:attribute_options) { { example: example, regexp: /Bob/ } }
225
212
 
226
- its(:example){ should == example }
213
+ its(:example) { should eq example }
227
214
 
228
215
  context 'that is not valid' do
229
- let(:example){ "Frank" }
216
+ let(:example) { 'Frank' }
230
217
  it 'raises a validation error' do
231
- expect{
218
+ expect do
232
219
  subject.example
233
- }.to raise_error(Attributor::AttributorException, /Error generating example/)
220
+ end.to raise_error(Attributor::AttributorException, /Error generating example/)
234
221
  end
235
222
  end
236
223
  end
@@ -239,17 +226,17 @@ describe Attributor::Attribute do
239
226
  context 'example_from_options' do
240
227
  let(:example) { nil }
241
228
  let(:generated_example) { example }
242
- let(:attribute_options) { {:example => example} }
243
- let(:parent){ nil }
244
- let(:context){ Attributor::DEFAULT_ROOT_CONTEXT}
229
+ let(:attribute_options) { { example: example } }
230
+ let(:parent) { nil }
231
+ let(:context) { Attributor::DEFAULT_ROOT_CONTEXT }
245
232
 
246
- subject(:example_result) { attribute.example_from_options( parent, context ) }
233
+ subject(:example_result) { attribute.example_from_options(parent, context) }
247
234
  before do
248
- attribute.should_receive(:load).with( generated_example , an_instance_of(Array) ).and_call_original
235
+ expect(attribute).to receive(:load).with(generated_example, an_instance_of(Array)).and_call_original
249
236
  end
250
237
 
251
238
  context 'with a string' do
252
- let(:example) { "example" }
239
+ let(:example) { 'example' }
253
240
 
254
241
  it { should be example }
255
242
  end
@@ -265,9 +252,9 @@ describe Attributor::Attribute do
265
252
  let(:generated_example) { /\w+/.gen }
266
253
 
267
254
  it 'calls #gen on the regexp' do
268
- example.should_receive(:gen).and_return(generated_example)
255
+ expect(example).to receive(:gen).and_return(generated_example)
269
256
 
270
- example_result.should =~ example
257
+ expect(example_result).to match example
271
258
  end
272
259
 
273
260
  context 'for a type with a non-String native_type' do
@@ -276,113 +263,109 @@ describe Attributor::Attribute do
276
263
  let(:generated_example) { /\d{5}/.gen }
277
264
 
278
265
  it 'coerces the example value properly' do
279
- example.should_receive(:gen).and_return(generated_example)
280
- type.should_receive(:load).and_call_original
266
+ expect(example).to receive(:gen).and_return(generated_example)
267
+ expect(type).to receive(:load).and_call_original
281
268
 
282
- example_result.should be_kind_of(type.native_type)
269
+ expect(example_result).to be_kind_of(type.native_type)
283
270
  end
284
271
  end
285
-
286
272
  end
287
273
 
288
274
  context 'with a proc' do
289
- let(:parent){ Object.new }
275
+ let(:parent) { Object.new }
290
276
 
291
277
  context 'with one argument' do
292
- let(:example) { lambda { |obj| 'ok' } }
278
+ let(:example) { ->(_obj) { 'ok' } }
293
279
  let(:generated_example) { 'ok' }
294
280
 
295
281
  before do
296
- example.should_receive(:call).with(parent).and_return(generated_example)
282
+ expect(example).to receive(:call).with(parent).and_return(generated_example)
297
283
  end
298
284
 
299
285
  it 'passes any given parent through to the example proc' do
300
- example_result.should == 'ok'
286
+ expect(example_result).to eq 'ok'
301
287
  end
302
288
  end
303
289
 
304
290
  context 'with two arguments' do
305
- let(:example) { lambda { |obj, context| "#{context} ok" } }
291
+ let(:example) { ->(_obj, context) { "#{context} ok" } }
306
292
  let(:generated_example) { "#{context} ok" }
307
- let(:context){ ['some_context'] }
293
+ let(:context) { ['some_context'] }
308
294
  before do
309
- example.should_receive(:call).with(parent, context).and_return(generated_example)
295
+ expect(example).to receive(:call).with(parent, context).and_return(generated_example)
310
296
  end
311
297
 
312
298
  it 'passes any given parent through to the example proc' do
313
- example_result.should == "#{context} ok"
299
+ expect(example_result).to eq "#{context} ok"
314
300
  end
315
301
  end
316
-
317
302
  end
318
303
 
319
304
  context 'with an Collection (of Strings)' do
320
305
  let(:type) { Attributor::Collection.of(String) }
321
- let(:example) { ["one"] }
322
- it { should == example }
306
+ let(:example) { ['one'] }
307
+ it { should eq example }
323
308
  end
324
-
325
309
  end
326
310
 
327
311
  context 'load' do
328
- let(:context){ ['context'] }
312
+ let(:context) { ['context'] }
329
313
  let(:value) { '1' }
330
314
 
331
315
  it 'delegates to type.load' do
332
- type.should_receive(:load).with(value,context, {})
333
- attribute.load(value,context)
316
+ expect(type).to receive(:load).with(value, context, {})
317
+ attribute.load(value, context)
334
318
  end
335
319
 
336
320
  it 'passes options to type.load' do
337
- type.should_receive(:load).with(value, context, foo: 'bar')
321
+ expect(type).to receive(:load).with(value, context, foo: 'bar')
338
322
  attribute.load(value, context, foo: 'bar')
339
323
  end
340
324
 
341
325
  context 'applying default values' do
342
326
  let(:value) { nil }
343
- let(:default_value) { "default value" }
344
- let(:attribute_options) { {:default => default_value} }
327
+ let(:default_value) { 'default value' }
328
+ let(:attribute_options) { { default: default_value } }
345
329
 
346
330
  subject(:result) { attribute.load(value) }
347
331
 
348
332
  context 'for nil' do
349
- it { should == default_value}
333
+ it { should eq default_value }
350
334
  end
351
335
 
352
336
  context 'for false' do
353
337
  let(:type) { Attributor::Boolean }
354
338
  let(:default_value) { false }
355
- it { should == default_value}
356
-
339
+ it { should eq default_value }
357
340
  end
358
341
 
359
342
  context 'for a Proc-based default value' do
360
- let(:context){ ["$"] }
361
- subject(:result){ attribute.load(value,context) }
362
-
343
+ let(:context) { ['$'] }
344
+ subject(:result) { attribute.load(value, context) }
363
345
 
364
346
  context 'with no arguments arguments' do
365
- let(:default_value) { proc { "no_params" } }
366
- it { should == default_value.call }
347
+ let(:default_value) { proc { 'no_params' } }
348
+ it { should eq default_value.call }
367
349
  end
368
350
 
369
351
  context 'with 1 argument (the parent)' do
370
- let(:default_value) { proc {|parent| "parent is fake: #{parent.class}" } }
371
- it { should == "parent is fake: Attributor::FakeParent" }
352
+ let(:default_value) { proc { |parent| "parent is fake: #{parent.class}" } }
353
+ it { should eq 'parent is fake: Attributor::FakeParent' }
372
354
  end
373
355
 
374
356
  context 'with 2 argument (the parent and the contents)' do
375
- let(:default_value) { proc {|parent,context| "parent is fake: #{parent.class} and context is: #{context}" } }
376
- it { should == "parent is fake: Attributor::FakeParent and context is: [\"$\"]"}
357
+ let(:default_value) { proc { |parent, context| "parent is fake: #{parent.class} and context is: #{context}" } }
358
+ it { should eq 'parent is fake: Attributor::FakeParent and context is: ["$"]' }
377
359
  end
378
360
 
379
361
  context 'which attempts to use the parent (which is not supported for the moment)' do
380
- let(:default_value) { proc {|parent| "any parent method should spit out warning: [#{parent.something}]" } }
381
- it "should output a warning" do
362
+ let(:default_value) { proc { |parent| "any parent method should spit out warning: [#{parent.something}]" } }
363
+ it 'should output a warning' do
382
364
  begin
383
- old_verbose, $VERBOSE = $VERBOSE, nil
384
- Kernel.should_receive(:warn).and_call_original
385
- attribute.load(value,context).should == "any parent method should spit out warning: []"
365
+ old_verbose = $VERBOSE
366
+ $VERBOSE = nil
367
+ expect(Kernel).to receive(:warn).and_call_original
368
+ expect(attribute.load(value, context)).to eq 'any parent method should spit out warning: []'
386
369
  ensure
387
370
  $VERBOSE = old_verbose
388
371
  end
@@ -392,61 +375,56 @@ describe Attributor::Attribute do
392
375
  end
393
376
 
394
377
  context 'validating a value' do
395
-
396
378
  context '#validate' do
397
379
  context 'applying attribute options' do
398
380
  context ':required' do
399
- let(:attribute_options) { {:required => true} }
381
+ let(:attribute_options) { { required: true } }
400
382
  context 'with a nil value' do
401
383
  let(:value) { nil }
402
384
  it 'returns an error' do
403
- attribute.validate(value, context).first.should == 'Attribute context is required'
385
+ expect(attribute.validate(value, context).first).to eq 'Attribute context is required'
404
386
  end
405
387
  end
406
388
  end
407
389
 
408
390
  context ':values' do
409
- let(:values) { ['one','two'] }
410
- let(:attribute_options) { {:values => values} }
391
+ let(:values) { %w(one two) }
392
+ let(:attribute_options) { { values: values } }
411
393
  let(:value) { nil }
412
394
 
413
- subject(:errors) { attribute.validate(value, context)}
395
+ subject(:errors) { attribute.validate(value, context) }
414
396
 
415
397
  context 'with a value that is allowed' do
416
- let(:value) { "one" }
398
+ let(:value) { 'one' }
417
399
  it 'returns no errors' do
418
- errors.should be_empty
400
+ expect(errors).to be_empty
419
401
  end
420
402
  end
421
403
 
422
404
  context 'with a value that is not allowed' do
423
- let(:value) { "three" }
405
+ let(:value) { 'three' }
424
406
  it 'returns an error indicating the problem' do
425
- errors.first.should =~ /is not within the allowed values/
407
+ expect(errors.first).to match(/is not within the allowed values/)
426
408
  end
427
-
428
409
  end
429
410
  end
430
-
431
-
432
411
  end
433
412
 
434
413
  it 'calls the right validate_X methods?' do
435
- attribute.should_receive(:validate_type).with(value, context).and_call_original
436
- attribute.should_not_receive(:validate_dependency)
437
- type.should_receive(:validate).and_call_original
414
+ expect(attribute).to receive(:validate_type).with(value, context).and_call_original
415
+ expect(attribute).not_to receive(:validate_dependency)
416
+ expect(type).to receive(:validate).and_call_original
438
417
  attribute.validate(value, context)
439
418
  end
440
-
441
419
  end
442
420
 
443
421
  context '#validate_type' do
444
- subject(:errors) { attribute.validate_type(value, context)}
422
+ subject(:errors) { attribute.validate_type(value, context) }
445
423
 
446
424
  context 'with a value of the right type' do
447
- let(:value) { "one" }
425
+ let(:value) { 'one' }
448
426
  it 'returns no errors' do
449
- errors.should be_empty
427
+ expect(errors).to be_empty
450
428
  end
451
429
  end
452
430
 
@@ -454,30 +432,26 @@ describe Attributor::Attribute do
454
432
  let(:value) { 1 }
455
433
 
456
434
  it 'returns errors' do
457
- errors.should_not be_empty
458
- errors.first.should =~ /is of the wrong type/
435
+ expect(errors).not_to be_empty
436
+ expect(errors.first).to match(/is of the wrong type/)
459
437
  end
460
-
461
438
  end
462
-
463
-
464
439
  end
465
440
 
466
441
  context '#validate_missing_value' do
467
- let(:key) { "$.instance.ssh_key.name" }
442
+ let(:key) { '$.instance.ssh_key.name' }
468
443
  let(:value) { /\w+/.gen }
469
444
 
470
- let(:attribute_options) { {:required_if => key} }
445
+ let(:attribute_options) { { required_if: key } }
471
446
 
472
- let(:ssh_key) { double("ssh_key", :name => value) }
473
- let(:instance) { double("instance", :ssh_key => ssh_key) }
447
+ let(:ssh_key) { double('ssh_key', name: value) }
448
+ let(:instance) { double('instance', ssh_key: ssh_key) }
474
449
 
475
450
  before { Attributor::AttributeResolver.current.register('instance', instance) }
476
451
 
477
- let(:attribute_context) { ['$','params','key_material'] }
452
+ let(:attribute_context) { ['$', 'params', 'key_material'] }
478
453
  subject(:errors) { attribute.validate_missing_value(attribute_context) }
479
454
 
480
-
481
455
  context 'for a simple dependency without a predicate' do
482
456
  context 'that is satisfied' do
483
457
  it { should_not be_empty }
@@ -490,36 +464,33 @@ describe Attributor::Attribute do
490
464
  end
491
465
 
492
466
  context 'with a dependency that has a predicate' do
493
- let(:value) { "default_ssh_key_name" }
494
- #subject(:errors) { attribute.validate_missing_value('') }
467
+ let(:value) { 'default_ssh_key_name' }
468
+ # subject(:errors) { attribute.validate_missing_value('') }
495
469
 
496
470
  context 'where the target attribute exists, and matches the predicate' do
497
- let(:attribute_options) { {:required_if => {key => /default/} } }
471
+ let(:attribute_options) { { required_if: { key => /default/ } } }
498
472
 
499
473
  it { should_not be_empty }
500
474
 
501
- its(:first) { should =~ /Attribute #{Regexp.quote(Attributor.humanize_context( attribute_context ))} is required when #{Regexp.quote(key)} matches/ }
475
+ its(:first) { should match(/Attribute #{Regexp.quote(Attributor.humanize_context(attribute_context))} is required when #{Regexp.quote(key)} matches/) }
502
476
  end
503
477
 
504
478
  context 'where the target attribute exists, but does not match the predicate' do
505
- let(:attribute_options) { {:required_if => {key => /other/} } }
479
+ let(:attribute_options) { { required_if: { key => /other/ } } }
506
480
 
507
481
  it { should be_empty }
508
482
  end
509
483
 
510
484
  context 'where the target attribute does not exist' do
511
- let(:attribute_options) { {:required_if => {key => /default/} } }
512
- let(:ssh_key) { double("ssh_key", :name => nil) }
485
+ let(:attribute_options) { { required_if: { key => /default/ } } }
486
+ let(:ssh_key) { double('ssh_key', name: nil) }
513
487
 
514
488
  it { should be_empty }
515
489
  end
516
490
  end
517
-
518
491
  end
519
-
520
492
  end
521
493
 
522
-
523
494
  context 'for an attribute for a subclass of Model' do
524
495
  let(:type) { Chicken }
525
496
  let(:type_options) { Chicken.options }
@@ -527,46 +498,43 @@ describe Attributor::Attribute do
527
498
  subject(:attribute) { Attributor::Attribute.new(type, attribute_options) }
528
499
 
529
500
  it 'has attributes' do
530
- attribute.attributes.should == type.attributes
501
+ expect(attribute.attributes).to eq type.attributes
531
502
  end
532
503
 
533
- #it 'has compiled_definition' do
534
- # attribute.compiled_definition.should == type.definition
535
- #end
536
-
504
+ # it 'has compiled_definition' do
505
+ # attribute.compiled_definition.should eq type.definition
506
+ # end
537
507
 
538
508
  it 'merges its options with those of the compiled_definition' do
539
- attribute.options.should == attribute_options.merge(type_options)
509
+ expect(attribute.options).to eq attribute_options.merge(type_options)
540
510
  end
541
511
 
542
512
  it 'describe handles sub-attributes nicely' do
543
513
  describe = attribute.describe(false)
544
514
 
545
- describe[:type][:name].should == type.name
546
- common_options = attribute_options.select{|k,v| Attributor::Attribute.TOP_LEVEL_OPTIONS.include? k }
547
- special_options = attribute_options.reject{|k,v| Attributor::Attribute.TOP_LEVEL_OPTIONS.include? k }
548
- common_options.each do |k,v|
549
- describe[k].should == v
515
+ expect(describe[:type][:name]).to eq type.name
516
+ common_options = attribute_options.select { |k, _v| Attributor::Attribute.TOP_LEVEL_OPTIONS.include? k }
517
+ special_options = attribute_options.reject { |k, _v| Attributor::Attribute.TOP_LEVEL_OPTIONS.include? k }
518
+ common_options.each do |k, v|
519
+ expect(describe[k]).to eq v
550
520
  end
551
- special_options.each do |k,v|
552
- describe[:options][k].should == v
521
+ special_options.each do |k, v|
522
+ expect(describe[:options][k]).to eq v
553
523
  end
554
- type_options.each do |k,v|
555
- describe[:options][k].should == v
524
+ type_options.each do |k, v|
525
+ expect(describe[:options][k]).to eq v
556
526
  end
557
527
 
558
-
559
- attribute.attributes.each do |name, attr|
560
- describe[:type][:attributes].should have_key(name)
528
+ attribute.attributes.each do |name, _attr|
529
+ expect(describe[:type][:attributes]).to have_key(name)
561
530
  end
562
-
563
531
  end
564
532
 
565
533
  it 'supports deterministic examples' do
566
- example_1 = attribute.example(["Chicken context"])
567
- example_2 = attribute.example(["Chicken context"])
534
+ example_1 = attribute.example(['Chicken context'])
535
+ example_2 = attribute.example(['Chicken context'])
568
536
 
569
- example_1.attributes.should eq(example_2.attributes)
537
+ expect(example_1.attributes).to eq(example_2.attributes)
570
538
  end
571
539
 
572
540
  context '#validate' do
@@ -575,29 +543,27 @@ describe Attributor::Attribute do
575
543
 
576
544
  it 'validates sub-attributes' do
577
545
  errors = attribute.validate(chicken)
578
- errors.should be_empty
546
+ expect(errors).to be_empty
579
547
  end
580
548
 
581
549
  context 'with a failing validation' do
582
- subject(:chicken) { Chicken.example(age: 150, email: "foo") }
550
+ subject(:chicken) { Chicken.example(age: 150, email: 'foo') }
583
551
  let(:email_validation_response) { ["$.email value \(#{chicken.email}\) does not match regexp (/@/)"] }
584
552
  let(:age_validation_response) { ["$.age value \(#{chicken.age}\) is larger than the allowed max (120)"] }
585
553
 
586
554
  it 'collects sub-attribute validation errors' do
587
555
  errors = attribute.validate(chicken)
588
- errors.should =~ (age_validation_response | email_validation_response)
556
+ expect(errors).to match_array(age_validation_response | email_validation_response)
589
557
  end
590
558
  end
591
-
592
559
  end
593
560
 
594
-
595
561
  context '#validate_missing_value' do
596
562
  let(:type) { Duck }
597
563
  let(:attribute_name) { nil }
598
564
  let(:attribute) { Duck.attributes[attribute_name] }
599
565
 
600
- let(:attribute_context) { ['$','duck',"#{attribute_name}"] }
566
+ let(:attribute_context) { ['$', 'duck', attribute_name.to_s] }
601
567
  subject(:errors) { attribute.validate_missing_value(attribute_context) }
602
568
 
603
569
  before do
@@ -616,7 +582,7 @@ describe Attributor::Attribute do
616
582
 
617
583
  context 'where the target attribute exists, and matches the predicate' do
618
584
  it { should_not be_empty }
619
- its(:first) { should == "Attribute $.duck.email is required when name (for $.duck) is present." }
585
+ its(:first) { should eq 'Attribute $.duck.email is required when name (for $.duck) is present.' }
620
586
  end
621
587
  context 'where the target attribute does not exist' do
622
588
  before do
@@ -626,7 +592,6 @@ describe Attributor::Attribute do
626
592
  end
627
593
  end
628
594
 
629
-
630
595
  context 'for a dependency with a predicate' do
631
596
  let(:attribute_name) { :age }
632
597
 
@@ -639,7 +604,7 @@ describe Attributor::Attribute do
639
604
 
640
605
  context 'where the target attribute exists, and matches the predicate' do
641
606
  it { should_not be_empty }
642
- its(:first) { should =~ /Attribute #{Regexp.quote('$.duck.age')} is required when name #{Regexp.quote('(for $.duck)')} matches/ }
607
+ its(:first) { should match(/Attribute #{Regexp.quote('$.duck.age')} is required when name #{Regexp.quote('(for $.duck)')} matches/) }
643
608
  end
644
609
 
645
610
  context 'where the target attribute exists, and does not match the predicate' do
@@ -655,69 +620,62 @@ describe Attributor::Attribute do
655
620
  end
656
621
  it { should be_empty }
657
622
  end
658
-
659
623
  end
660
-
661
624
  end
662
-
663
625
  end
664
626
  end
665
627
 
666
628
  context 'for a Collection' do
667
629
  context 'of non-Model (or Struct) type' do
668
630
  let(:member_type) { Attributor::Integer }
669
- let(:type) { Attributor::Collection.of(member_type)}
670
- let(:member_options) { {:max => 10} }
671
- let(:attribute_options) { {:member_options => member_options} }
631
+ let(:type) { Attributor::Collection.of(member_type) }
632
+ let(:member_options) { { max: 10 } }
633
+ let(:attribute_options) { { member_options: member_options } }
672
634
 
673
635
  context 'the member_attribute of that type' do
674
636
  subject(:member_attribute) { attribute.type.member_attribute }
675
637
 
676
- it { should be_kind_of(Attributor::Attribute)}
638
+ it { should be_kind_of(Attributor::Attribute) }
677
639
  its(:type) { should be(member_type) }
678
640
  its(:options) { should eq(member_options) }
679
641
  end
680
642
 
681
- context "working with members" do
682
- let(:values) { ['1',2,12] }
643
+ context 'working with members' do
644
+ let(:values) { ['1', 2, 12] }
683
645
 
684
646
  it 'loads' do
685
- attribute.load(values).should =~ [1,2,12]
647
+ expect(attribute.load(values)).to match_array [1, 2, 12]
686
648
  end
687
649
 
688
650
  it 'validates' do
689
651
  object = attribute.load(values)
690
652
  errors = attribute.validate(object)
691
653
 
692
- errors.should have(1).item
693
- errors[0].should =~ /value \(12\) is larger/
654
+ expect(errors).to have(1).item
655
+ expect(errors[0]).to match(/value \(12\) is larger/)
694
656
  end
695
657
  end
696
-
697
-
698
658
  end
699
659
 
700
660
  context 'of a Model (or Struct) type' do
701
- subject(:attribute) { Attributor::Attribute.new(type, attribute_options, &attribute_block) }
661
+ subject(:attribute) { Attributor::Attribute.new(type, attribute_options, &attribute_block) }
702
662
 
703
- let(:attribute_block) { Proc.new{ attribute :angry , required: true } }
704
- let(:attribute_options) { {reference: Chicken, member_options: member_options} }
663
+ let(:attribute_block) { proc { attribute :angry, required: true } }
664
+ let(:attribute_options) { { reference: Chicken, member_options: member_options } }
705
665
  let(:member_type) { Attributor::Struct }
706
666
  let(:type) { Attributor::Collection.of(member_type) }
707
667
  let(:member_options) { {} }
708
668
 
709
669
  context 'the member_attribute of that type' do
710
670
  subject(:member_attribute) { attribute.type.member_attribute }
711
- it { should be_kind_of(Attributor::Attribute)}
671
+ it { should be_kind_of(Attributor::Attribute) }
712
672
  its(:options) { should eq(member_options.merge(reference: Chicken, identity: :email)) }
713
673
  its(:attributes) { should have_key :angry }
714
674
  it 'inherited the type and options from the reference' do
715
- member_attribute.attributes[:angry].type.should be(Chicken.attributes[:angry].type)
716
- member_attribute.attributes[:angry].options.should eq(Chicken.attributes[:angry].options.merge(required: true))
675
+ expect(member_attribute.attributes[:angry].type).to be(Chicken.attributes[:angry].type)
676
+ expect(member_attribute.attributes[:angry].options).to eq(Chicken.attributes[:angry].options.merge(required: true))
717
677
  end
718
678
  end
719
-
720
679
  end
721
680
  end
722
-
723
681
  end