attributor 5.0.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
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