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,9 +1,8 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
2
 
3
3
  describe Attributor::Ids do
4
-
5
4
  context '.for' do
6
- let(:chickens) { 10.times.collect { Chicken.example } }
5
+ let(:chickens) { Array.new(10) { Chicken.example } }
7
6
 
8
7
  let(:emails) { chickens.collect(&:email) }
9
8
  let(:value) { emails.join(',') }
@@ -13,20 +12,19 @@ describe Attributor::Ids do
13
12
  its(:member_attribute) { should be(Chicken.attributes[:email]) }
14
13
 
15
14
  it 'loads' do
16
- ids.load(value).should =~ emails
15
+ expect(ids.load(value)).to match_array emails
17
16
  end
18
17
 
19
18
  it 'generates valid, loadable examples' do
20
- ids.validate(ids.load(ids.example)).should be_empty
19
+ expect(ids.validate(ids.load(ids.example))).to be_empty
21
20
  end
22
-
23
21
  end
24
22
 
25
23
  context 'attempting to define it as a collection using .of(type)' do
26
24
  it 'raises an error' do
27
- expect{
25
+ expect do
28
26
  Attributor::Ids.of(Chicken)
29
- }.to raise_error(/Defining Ids.of\(type\) is not allowed/)
27
+ end.to raise_error(/Defining Ids.of\(type\) is not allowed/)
30
28
  end
31
29
  end
32
30
  end
@@ -1,23 +1,21 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
2
 
3
3
  describe Attributor::Integer do
4
-
5
4
  subject(:type) { Attributor::Integer }
6
5
 
7
6
  it 'it is not Dumpable' do
8
- type.new.is_a?(Attributor::Dumpable).should_not be(true)
7
+ expect(type.new.is_a?(Attributor::Dumpable)).not_to be(true)
9
8
  end
10
9
 
11
10
  context '.example' do
12
-
13
11
  context 'when :min and :max are unspecified' do
14
12
  context 'valid cases' do
15
13
  it "returns an Integer in the range [0,#{Attributor::Integer::EXAMPLE_RANGE}]" do
16
14
  20.times do
17
15
  value = type.example
18
- value.should be_a(::Integer)
19
- value.should <= Attributor::Integer::EXAMPLE_RANGE
20
- value.should >= 0
16
+ expect(value).to be_a(::Integer)
17
+ expect(value).to be <= Attributor::Integer::EXAMPLE_RANGE
18
+ expect(value).to be >= 0
21
19
  end
22
20
  end
23
21
  end
@@ -25,13 +23,13 @@ describe Attributor::Integer do
25
23
 
26
24
  context 'when :min is unspecified' do
27
25
  context 'valid cases' do
28
- [5, 100000000000000000000, -100000000000000000000].each do |max|
26
+ [5, 100_000_000_000_000_000_000, -100_000_000_000_000_000_000].each do |max|
29
27
  it "returns an Integer in the range [,#{max.inspect}]" do
30
28
  20.times do
31
- value = type.example(nil, options: {max: max})
32
- value.should be_a(::Integer)
33
- value.should <= max
34
- value.should >= max - Attributor::Integer::EXAMPLE_RANGE
29
+ value = type.example(nil, options: { max: max })
30
+ expect(value).to be_a(::Integer)
31
+ expect(value).to be <= max
32
+ expect(value).to be >= max - Attributor::Integer::EXAMPLE_RANGE
35
33
  end
36
34
  end
37
35
  end
@@ -40,10 +38,10 @@ describe Attributor::Integer do
40
38
  context 'invalid cases' do
41
39
  ['invalid', false].each do |max|
42
40
  it "raises for the invalid range [,#{max.inspect}]" do
43
- expect {
44
- value = type.example(nil, options: {max: max})
45
- value.should be_a(::Integer)
46
- }.to raise_error(Attributor::AttributorException, "Invalid range: [, #{max.inspect}]")
41
+ expect do
42
+ value = type.example(nil, options: { max: max })
43
+ expect(value).to be_a(::Integer)
44
+ end.to raise_error(Attributor::AttributorException, "Invalid range: [, #{max.inspect}]")
47
45
  end
48
46
  end
49
47
  end
@@ -51,13 +49,13 @@ describe Attributor::Integer do
51
49
 
52
50
  context 'when :max is unspecified' do
53
51
  context 'valid cases' do
54
- [1, -100000000000000000000, 100000000000000000000].each do |min|
52
+ [1, -100_000_000_000_000_000_000, 100_000_000_000_000_000_000].each do |min|
55
53
  it "returns an Integer in the range [#{min.inspect},]" do
56
54
  20.times do
57
- value = type.example(nil, options: {min: min})
58
- value.should be_a(::Integer)
59
- value.should <= min + Attributor::Integer::EXAMPLE_RANGE
60
- value.should >= min
55
+ value = type.example(nil, options: { min: min })
56
+ expect(value).to be_a(::Integer)
57
+ expect(value).to be <= min + Attributor::Integer::EXAMPLE_RANGE
58
+ expect(value).to be >= min
61
59
  end
62
60
  end
63
61
  end
@@ -66,10 +64,10 @@ describe Attributor::Integer do
66
64
  context 'invalid cases' do
67
65
  ['invalid', false].each do |min|
68
66
  it "raises for the invalid range [#{min.inspect},]" do
69
- expect {
70
- value = type.example(nil, options: {min: min})
71
- value.should be_a(::Integer)
72
- }.to raise_error(Attributor::AttributorException, "Invalid range: [#{min.inspect},]")
67
+ expect do
68
+ value = type.example(nil, options: { min: min })
69
+ expect(value).to be_a(::Integer)
70
+ end.to raise_error(Attributor::AttributorException, "Invalid range: [#{min.inspect},]")
73
71
  end
74
72
  end
75
73
  end
@@ -78,33 +76,32 @@ describe Attributor::Integer do
78
76
  context 'when :min and :max are specified' do
79
77
  context 'valid cases' do
80
78
  [
81
- [1,1],
82
- [1,5],
83
- [-2,-2],
84
- [-3,2],
85
- [-1000000000000000,1000000000000000]
79
+ [1, 1],
80
+ [1, 5],
81
+ [-2, -2],
82
+ [-3, 2],
83
+ [-1_000_000_000_000_000, 1_000_000_000_000_000]
86
84
  ].each do |min, max|
87
85
  it "returns an Integer in the range [#{min.inspect},#{max.inspect}]" do
88
86
  20.times do
89
- value = type.example(nil, options: {max: max, min: min})
90
- value.should <= max
91
- value.should >= min
87
+ value = type.example(nil, options: { max: max, min: min })
88
+ expect(value).to be <= max
89
+ expect(value).to be >= min
92
90
  end
93
91
  end
94
92
  end
95
93
  end
96
94
 
97
95
  context 'invalid cases' do
98
- [[1,-1], [1,"5"], ["-2",4], [false, false], [true, true]].each do |min, max|
96
+ [[1, -1], [1, '5'], ['-2', 4], [false, false], [true, true]].each do |min, max|
99
97
  it "raises for the invalid range [#{min.inspect}, #{max.inspect}]" do
100
- opts = {options: {max: max, min: min}}
101
- expect {
98
+ opts = { options: { max: max, min: min } }
99
+ expect do
102
100
  type.example(nil, opts)
103
- }.to raise_error(Attributor::AttributorException, "Invalid range: [#{min.inspect}, #{max.inspect}]")
101
+ end.to raise_error(Attributor::AttributorException, "Invalid range: [#{min.inspect}, #{max.inspect}]")
104
102
  end
105
103
  end
106
104
  end
107
-
108
105
  end
109
106
  end
110
107
 
@@ -112,29 +109,26 @@ describe Attributor::Integer do
112
109
  let(:value) { nil }
113
110
 
114
111
  it 'returns nil for nil' do
115
- type.load(nil).should be(nil)
112
+ expect(type.load(nil)).to be(nil)
116
113
  end
117
114
 
118
115
  context 'for incoming integer values' do
119
116
  let(:value) { 1 }
120
117
 
121
118
  it 'returns the incoming value' do
122
- type.load(value).should be(value)
119
+ expect(type.load(value)).to be(value)
123
120
  end
124
121
  end
125
122
 
126
123
  context 'for incoming string values' do
127
-
128
-
129
124
  context 'that are valid integers' do
130
125
  let(:value) { '1024' }
131
126
  it 'decodes it if the string represents an integer' do
132
- type.load(value).should == 1024
127
+ expect(type.load(value)).to eq 1024
133
128
  end
134
129
  end
135
130
 
136
131
  context 'that are not valid integers' do
137
-
138
132
  context 'with simple alphanumeric text' do
139
133
  let(:value) { 'not an integer' }
140
134
 
@@ -149,10 +143,7 @@ describe Attributor::Integer do
149
143
  expect { type.load(value) }.to raise_error(/invalid value/)
150
144
  end
151
145
  end
152
-
153
146
  end
154
-
155
147
  end
156
148
  end
157
149
  end
158
-
@@ -11,46 +11,52 @@ describe Attributor::Model do
11
11
  Class.new(Attributor::Model) do
12
12
  attributes do
13
13
  raise 'sorry :('
14
- attribute :name, String
15
14
  end
16
15
  end
17
16
  end
18
17
 
19
18
  it 'throws original exception upon first run' do
20
- lambda {
19
+ expect do
21
20
  broken_model.attributes
22
- }.should raise_error(RuntimeError, 'sorry :(')
21
+ end.to raise_error(RuntimeError, 'sorry :(')
23
22
  end
24
23
 
25
24
  it 'throws InvalidDefinition for subsequent access' do
26
- broken_model.attributes rescue nil
25
+ begin
26
+ broken_model.attributes
27
+ rescue
28
+ nil
29
+ end
27
30
 
28
- lambda {
31
+ expect do
29
32
  broken_model.attributes
30
- }.should raise_error(Attributor::InvalidDefinition)
33
+ end.to raise_error(Attributor::InvalidDefinition)
31
34
  end
32
35
 
33
36
  it 'throws for any attempts at using of an instance of it' do
34
- broken_model.attributes rescue nil
37
+ begin
38
+ broken_model.attributes
39
+ rescue
40
+ nil
41
+ end
35
42
 
36
43
  instance = broken_model.new
37
- lambda {
44
+ expect do
38
45
  instance.name
39
- }.should raise_error(Attributor::InvalidDefinition)
46
+ end.to raise_error(Attributor::InvalidDefinition)
40
47
  end
41
-
42
48
  end
43
49
  end
44
50
 
45
51
  context 'class methods' do
46
- let(:context){ ["root","subattr"] }
52
+ let(:context) { %w(root subattr) }
47
53
 
48
54
  its(:native_type) { should eq(Chicken) }
49
55
 
50
56
  context '.example' do
51
57
  subject(:chicken) { Chicken.example }
52
58
 
53
- let(:age_opts) { {options: Chicken.attributes[:age].options } }
59
+ let(:age_opts) { { options: Chicken.attributes[:age].options } }
54
60
  let(:age) { /\d{2}/.gen.to_i }
55
61
 
56
62
  context 'for a simple model' do
@@ -58,25 +64,25 @@ describe Attributor::Model do
58
64
 
59
65
  context 'and attribute without :example option' do
60
66
  before do
61
- Attributor::Integer.should_receive(:example).with(kind_of(Array), age_opts).and_return(age)
67
+ expect(Attributor::Integer).to receive(:example).with(kind_of(Array), age_opts).and_return(age)
62
68
  end
63
69
 
64
- its(:age) { should == age }
70
+ its(:age) { should eq age }
65
71
  end
66
72
 
67
73
  context 'and attribute with :example options' do
68
74
  before do
69
- Attributor::Integer.should_not_receive(:example) # due to lazy-evaluation of examples
70
- Attributor::String.should_not_receive(:example) # due to the :example option on the attribute
75
+ expect(Attributor::Integer).not_to receive(:example) # due to lazy-evaluation of examples
76
+ expect(Attributor::String).not_to receive(:example) # due to the :example option on the attribute
71
77
  end
72
- its(:email) { should =~ /\w+@.*\.example\.org/ }
78
+ its(:email) { should match(/\w+@.*\.example\.org/) }
73
79
  end
74
80
 
75
81
  context 'with given values' do
76
82
  let(:name) { 'Sir Clucksalot' }
77
- subject(:example) { Chicken.example(name: name)}
83
+ subject(:example) { Chicken.example(name: name) }
78
84
 
79
- its(:name) {should eq(name) }
85
+ its(:name) { should eq(name) }
80
86
  end
81
87
  end
82
88
 
@@ -92,14 +98,13 @@ describe Attributor::Model do
92
98
 
93
99
  its(:attributes) { should eq(some_chicken.attributes) }
94
100
  end
95
-
96
101
  end
97
102
 
98
103
  context 'with attributes that are also models' do
99
104
  subject(:turducken) { Turducken.example }
100
105
 
101
106
  its(:attributes) { should have_key(:chicken) }
102
- its(:chicken) { should be_kind_of(Chicken)}
107
+ its(:chicken) { should be_kind_of(Chicken) }
103
108
  end
104
109
 
105
110
  context 'with infinitely-expanding sub-attributes' do
@@ -117,20 +122,17 @@ describe Attributor::Model do
117
122
 
118
123
  it 'terminates example generation at MAX_EXAMPLE_DEPTH' do
119
124
  # call .child on example MAX_EXAMPLE_DEPTH times
120
- terminal_child = Attributor::Model::MAX_EXAMPLE_DEPTH.times.inject(example) do |object, i|
125
+ terminal_child = Attributor::Model::MAX_EXAMPLE_DEPTH.times.inject(example) do |object, _i|
121
126
  object.child
122
127
  end
123
128
  # after which .child will return nil
124
- terminal_child.child.should be(nil)
129
+ expect(terminal_child.child).to be(nil)
125
130
  # but simple attributes will be generated
126
- terminal_child.name.should_not be(nil)
131
+ expect(terminal_child.name).not_to be(nil)
127
132
  end
128
-
129
-
130
133
  end
131
134
  end
132
135
 
133
-
134
136
  context '.definition' do
135
137
  subject(:definition) { Chicken.definition }
136
138
 
@@ -141,48 +143,46 @@ describe Attributor::Model do
141
143
  end
142
144
  end
143
145
 
144
-
145
146
  context '.load' do
146
147
  let(:age) { 1 }
147
- let(:email) { "cluck@example.org" }
148
- let(:hash) { {:age => age, :email => email} }
148
+ let(:email) { 'cluck@example.org' }
149
+ let(:hash) { { age: age, email: email } }
149
150
 
150
151
  subject(:model) { Chicken.load(hash) }
151
152
 
152
153
  context 'with an instance of the model' do
153
154
  it 'returns the instance' do
154
- Chicken.load(model).should be(model)
155
+ expect(Chicken.load(model)).to be(model)
155
156
  end
156
157
  end
157
158
 
158
159
  context 'with a nil value' do
159
160
  it 'returns nil' do
160
- Chicken.load(nil).should be_nil
161
+ expect(Chicken.load(nil)).to be_nil
161
162
  end
162
163
 
163
164
  context 'with recurse: true' do
164
165
  subject(:turducken) { Turducken.load(nil, [], recurse: true) }
165
166
 
166
167
  it 'loads with default values' do
167
- turducken.name.should eq("Turkey McDucken")
168
- turducken.chicken.age.should be(1)
168
+ expect(turducken.name).to eq('Turkey McDucken')
169
+ expect(turducken.chicken.age).to be(1)
169
170
  end
170
-
171
171
  end
172
172
  end
173
173
 
174
174
  context 'with a JSON-serialized hash' do
175
- let(:context){ ['root','subattr'] }
176
- let(:expected_hash) { {"age" => age, "email" => email} }
175
+ let(:context) { %w(root subattr) }
176
+ let(:expected_hash) { { 'age' => age, 'email' => email } }
177
177
  let(:json) { hash.to_json }
178
178
  before do
179
- Chicken.should_receive(:from_hash).
180
- with(expected_hash,context, recurse: false)
181
- JSON.should_receive(:parse).with(json).and_call_original
179
+ expect(Chicken).to receive(:from_hash)
180
+ .with(expected_hash, context, recurse: false)
181
+ expect(JSON).to receive(:parse).with(json).and_call_original
182
182
  end
183
183
 
184
184
  it 'deserializes and calls from_hash' do
185
- Chicken.load(json,context)
185
+ Chicken.load(json, context)
186
186
  end
187
187
  end
188
188
 
@@ -190,36 +190,37 @@ describe Attributor::Model do
190
190
  let(:json) { "{'invalid'}" }
191
191
 
192
192
  it 'catches the error and reports it correctly' do
193
- JSON.should_receive(:parse).with(json).and_call_original
194
- expect {
195
- Chicken.load(json,context)
196
- }.to raise_error(Attributor::DeserializationError, /Error deserializing a String using JSON.*#{context.join('.')}/)
193
+ expect(JSON).to receive(:parse).with(json).and_call_original
194
+ expect do
195
+ Chicken.load(json, context)
196
+ end.to raise_error(Attributor::DeserializationError,
197
+ /Error deserializing a String using JSON.*#{context.join('.')}/)
197
198
  end
198
199
  end
199
200
 
200
-
201
201
  context 'with an invalid object type' do
202
202
  it 'raises some sort of error' do
203
- expect {
203
+ expect do
204
204
  Chicken.load(Object.new, context)
205
- }.to raise_error(Attributor::IncompatibleTypeError, /Type Chicken cannot load values of type Object.*#{context.join('.')}/)
205
+ end.to raise_error(Attributor::IncompatibleTypeError,
206
+ /Type Chicken cannot load values of type Object.*#{context.join('.')}/)
206
207
  end
207
208
  end
208
209
 
209
210
  context 'with an instance of different model' do
210
211
  it 'raises some sort of error' do
211
- expect {
212
+ expect do
212
213
  turducken = Turducken.example
213
- chicken = Chicken.load(turducken,context)
214
- }.to raise_error(Attributor::AttributorException, /Unknown key received/)
214
+ Chicken.load(turducken, context)
215
+ end.to raise_error(Attributor::AttributorException, /Unknown key received/)
215
216
  end
216
217
  end
217
218
 
218
- context "with a hash" do
219
+ context 'with a hash' do
219
220
  context 'for a complete set of attributes' do
220
221
  it 'loads the given attributes' do
221
- model.age.should == age
222
- model.email.should == email
222
+ expect(model.age).to eq age
223
+ expect(model.email).to eq email
223
224
  end
224
225
  end
225
226
 
@@ -227,52 +228,47 @@ describe Attributor::Model do
227
228
  let(:hash) { Hash.new }
228
229
 
229
230
  it 'sets the defaults' do
230
- model.age.should == 1
231
- model.email.should == nil
231
+ expect(model.age).to eq 1
232
+ expect(model.email).to be nil
232
233
  end
233
234
  end
234
235
 
235
236
  context 'for a superset of attributes' do
236
- let(:hash) { {"invalid_attribute" => "value"} }
237
+ let(:hash) { { 'invalid_attribute' => 'value' } }
237
238
 
238
239
  it 'raises an error' do
239
- expect {
240
+ expect do
240
241
  Chicken.load(hash, context)
241
- }.to raise_error(Attributor::AttributorException, /Unknown key received/)
242
- #raise_error(Attributor::AttributorException, /Unknown attributes.*#{context.join('.')}/)
242
+ end.to raise_error(Attributor::AttributorException, /Unknown key received/)
243
+ # raise_error(Attributor::AttributorException, /Unknown attributes.*#{context.join('.')}/)
243
244
  end
244
245
  end
245
246
 
246
247
  context 'loading with default values' do
247
248
  let(:reference) { Post }
248
- let(:options) { {reference: reference} }
249
+ let(:options) { { reference: reference } }
249
250
 
250
251
  let(:attribute_definition) do
251
252
  proc do
252
253
  attribute :title
253
- attribute :tags, default: ['stuff', 'things']
254
+ attribute :tags, default: %w(stuff things)
254
255
  end
255
256
  end
256
257
 
257
- let(:struct) { Attributor::Struct.construct(attribute_definition, options)}
258
-
259
- let(:data) { {title: 'my post'} }
258
+ let(:struct) { Attributor::Struct.construct(attribute_definition, options) }
260
259
 
261
- subject(:loaded) { struct.load(data) }
260
+ let(:data) { { title: 'my post' } }
262
261
 
262
+ subject(:loaded) { struct.load(data) }
263
263
 
264
264
  it 'validates' do
265
265
  expect(loaded.validate).to be_empty
266
266
  end
267
-
268
267
  end
269
268
  end
270
-
271
269
  end
272
-
273
270
  end
274
271
 
275
-
276
272
  context 'instance methods' do
277
273
  subject(:chicken) { Chicken.new }
278
274
 
@@ -283,36 +279,39 @@ describe Attributor::Model do
283
279
  end
284
280
 
285
281
  context 'initialize' do
286
-
287
282
  subject(:chicken) { Chicken.new(attributes_data) }
288
283
  context 'supports passing an initial hash object for attribute values' do
289
- let(:attributes_data){ {age: '1', email:'rooster@coup.com'} }
284
+ let(:attributes_data) { { age: '1', email: 'rooster@coup.com' } }
290
285
  it 'and sets them in loaded format onto the instance attributes' do
291
- Chicken.should_receive(:load).with(attributes_data).and_call_original
286
+ expect(Chicken).to receive(:load).with(attributes_data).and_call_original
292
287
  attributes_data.keys.each do |attr_name|
293
- Chicken.attributes[attr_name].should_receive(:load).with(attributes_data[attr_name],instance_of(Array), recurse: false).and_call_original
288
+ expect(Chicken.attributes[attr_name]).to receive(:load)
289
+ .with(attributes_data[attr_name], instance_of(Array), recurse: false)
290
+ .and_call_original
294
291
  end
295
- subject.age.should be(1)
296
- subject.email.should be(attributes_data[:email])
292
+ expect(subject.age).to be(1)
293
+ expect(subject.email).to be(attributes_data[:email])
297
294
  end
298
295
  end
299
296
  context 'supports passing a JSON encoded data object' do
300
- let(:attributes_hash){ {age: 1, email:'rooster@coup.com'} }
301
- let(:attributes_data){ JSON.dump(attributes_hash) }
297
+ let(:attributes_hash) { { age: 1, email: 'rooster@coup.com' } }
298
+ let(:attributes_data) { JSON.dump(attributes_hash) }
302
299
  it 'and sets them in loaded format onto the instance attributes' do
303
- Chicken.should_receive(:load).with(attributes_data).and_call_original
300
+ expect(Chicken).to receive(:load).with(attributes_data).and_call_original
304
301
  attributes_hash.keys.each do |attr_name|
305
- Chicken.attributes[attr_name].should_receive(:load).with(attributes_hash[attr_name],instance_of(Array), recurse: false).and_call_original
302
+ expect(Chicken.attributes[attr_name]).to receive(:load)
303
+ .with(attributes_hash[attr_name], instance_of(Array), recurse: false)
304
+ .and_call_original
306
305
  end
307
- subject.age.should be(1)
308
- subject.email.should == attributes_hash[:email]
306
+ expect(subject.age).to be(1)
307
+ expect(subject.email).to eq attributes_hash[:email]
309
308
  end
310
309
  end
311
310
  context 'supports passing a native model for the data object' do
312
- let(:attributes_data){ Chicken.example }
311
+ let(:attributes_data) { Chicken.example }
313
312
  it 'sets a new instance pointing to the exact same attributes (careful about modifications!)' do
314
313
  attributes_data.attributes.each do |attr_name, attr_value|
315
- subject.send(attr_name).should be(attr_value)
314
+ expect(subject.send(attr_name)).to be(attr_value)
316
315
  end
317
316
  end
318
317
  end
@@ -323,48 +322,45 @@ describe Attributor::Model do
323
322
  let(:age) { 1 }
324
323
  it 'gets and sets attributes' do
325
324
  chicken.age = age
326
- chicken.age.should == age
325
+ expect(chicken.age).to eq age
327
326
  end
328
327
  end
329
328
 
330
329
  context 'setting nil' do
331
330
  it 'assigns the default value if there is one' do
332
331
  chicken.age = nil
333
- chicken.age.should == 1
332
+ expect(chicken.age).to eq 1
334
333
  end
335
334
 
336
335
  it 'sets the value to nil if there is no default' do
337
336
  chicken.email = nil
338
- chicken.email.should == nil
337
+ expect(chicken.email).to be nil
339
338
  end
340
-
341
339
  end
342
340
 
343
341
  context 'for unknown attributes' do
344
342
  it 'raises an exception' do
345
- expect {
346
- chicken.invalid_attribute = 'value'
347
- }.to raise_error(NoMethodError, /undefined method/)
343
+ expect do
344
+ chicken.invalid_attribute = 'value'
345
+ end.to raise_error(NoMethodError, /undefined method/)
348
346
  end
349
347
  end
350
348
 
351
349
  context 'for false attributes' do
352
350
  subject(:person) { Person.example(okay: false) }
353
351
  it 'properly memoizes the value' do
354
- person.okay.should be(false)
355
- person.okay.should be(false) # second call to ensure we hit the memoized value
352
+ expect(person.okay).to be(false)
353
+ expect(person.okay).to be(false) # second call to ensure we hit the memoized value
356
354
  end
357
355
  end
358
356
  end
359
-
360
357
  end
361
358
 
362
-
363
359
  context 'validation' do
364
360
  context 'for simple models' do
365
361
  context 'that are valid' do
366
- subject(:chicken) { Chicken.example }
367
- its(:validate) { should be_empty}
362
+ subject(:chicken) { Chicken.example }
363
+ its(:validate) { should be_empty }
368
364
  end
369
365
  context 'that are invalid' do
370
366
  subject(:chicken) { Chicken.example(age: 150) }
@@ -375,17 +371,17 @@ describe Attributor::Model do
375
371
  context 'for models using the "requires" DSL' do
376
372
  subject(:address) { Address.load(state: 'CA') }
377
373
  its(:validate) { should_not be_empty }
378
- its(:validate) { should include "Key name is required for $." }
374
+ its(:validate) { should include 'Key name is required for $.' }
379
375
  end
380
376
  context 'for models with circular sub-attributes' do
381
377
  context 'that are valid' do
382
378
  subject(:person) { Person.example }
383
- its(:validate) { should be_empty}
379
+ its(:validate) { should be_empty }
384
380
  end
385
381
 
386
382
  context 'that are both invalid' do
387
- subject(:person){ Person.load( name: 'Joe', title: 'dude', okay: true )}
388
- let(:address){ Address.load( name: '1 Main St', state: 'ME' )}
383
+ subject(:person) { Person.load(name: 'Joe', title: 'dude', okay: true) }
384
+ let(:address) { Address.load(name: '1 Main St', state: 'ME') }
389
385
  before do
390
386
  person.address = address
391
387
  address.person = person
@@ -395,35 +391,29 @@ describe Attributor::Model do
395
391
 
396
392
  it 'recursively-validates sub-attributes with the right context' do
397
393
  title_error, state_error = person.validate('person')
398
- title_error.should =~ /^Attribute person\.title:/
399
- state_error.should =~ /^Attribute person\.address\.state:/
394
+ expect(title_error).to match(/^Attribute person\.title:/)
395
+ expect(state_error).to match(/^Attribute person\.address\.state:/)
400
396
  end
401
397
  end
402
-
403
398
  end
404
399
  end
405
400
 
406
-
407
401
  context '#dump' do
408
-
409
-
410
402
  context 'with circular references' do
411
403
  subject(:person) { Person.example }
412
404
  let(:output) { person.dump }
413
405
 
414
406
  it 'terminates' do
415
- expect {
407
+ expect do
416
408
  Person.example.dump
417
- }.to_not raise_error
409
+ end.to_not raise_error
418
410
  end
419
411
 
420
412
  it 'outputs "..." for circular references' do
421
- person.address.person.should be(person)
422
- output[:address][:person].should eq(Attributor::Model::CIRCULAR_REFERENCE_MARKER)
413
+ expect(person.address.person).to be(person)
414
+ expect(output[:address][:person]).to eq(Attributor::Model::CIRCULAR_REFERENCE_MARKER)
423
415
  end
424
-
425
416
  end
426
-
427
417
  end
428
418
 
429
419
  context 'extending' do
@@ -446,9 +436,8 @@ describe Attributor::Model do
446
436
  end
447
437
 
448
438
  it 'adds the attribute' do
449
- model.attributes.keys.should =~ [:id, :name, :timestamps]
439
+ expect(model.attributes.keys).to match_array [:id, :name, :timestamps]
450
440
  end
451
-
452
441
  end
453
442
 
454
443
  context 'adding to an inner-Struct' do
@@ -461,7 +450,7 @@ describe Attributor::Model do
461
450
  end
462
451
 
463
452
  it 'merges with sub-attributes' do
464
- model.attributes[:timestamps].attributes.keys.should =~ [:created_at, :updated_at]
453
+ expect(model.attributes[:timestamps].attributes.keys).to match_array [:created_at, :updated_at]
465
454
  end
466
455
  end
467
456
 
@@ -477,12 +466,12 @@ describe Attributor::Model do
477
466
  subject(:struct) { Attributor::Struct.construct(attributes_block, reference: Cormorant) }
478
467
 
479
468
  it 'supports defining sub-attributes using the proper reference' do
480
- struct.attributes[:neighbors].options[:required].should be true
481
- struct.attributes[:neighbors].type.member_attribute.type.attributes.keys.should =~ [:name, :age]
469
+ expect(struct.attributes[:neighbors].options[:required]).to be true
470
+ expect(struct.attributes[:neighbors].type.member_attribute.type.attributes.keys).to match_array [:name, :age]
482
471
 
483
472
  name_options = struct.attributes[:neighbors].type.member_attribute.type.attributes[:name].options
484
- name_options[:required].should be true
485
- name_options[:description].should eq 'Name of the Cormorant'
473
+ expect(name_options[:required]).to be true
474
+ expect(name_options[:description]).to eq 'Name of the Cormorant'
486
475
  end
487
476
  end
488
477
 
@@ -495,14 +484,10 @@ describe Attributor::Model do
495
484
  end
496
485
 
497
486
  it 'updates the type properly' do
498
- model.attributes[:id].type.should be(Attributor::String)
487
+ expect(model.attributes[:id].type).to be(Attributor::String)
499
488
  end
500
-
501
489
  end
502
-
503
-
504
490
  end
505
-
506
491
  end
507
492
 
508
493
  context 'with no defined attributes' do
@@ -518,9 +503,7 @@ describe Attributor::Model do
518
503
  its(:attributes) { should be_empty }
519
504
 
520
505
  it 'dumps as an empty hash' do
521
- example.dump.should eq({})
506
+ expect(example.dump).to eq({})
522
507
  end
523
-
524
508
  end
525
-
526
509
  end