attributor 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -142,8 +142,15 @@ module Attributor
142
142
 
143
143
 
144
144
  # Model-specific decoding and coercion of the attribute.
145
- def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
146
- return value if value.nil?
145
+ def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options)
146
+ if value.nil?
147
+ if recurse
148
+ value = {}
149
+ else
150
+ return value
151
+ end
152
+ end
153
+
147
154
  return value if value.kind_of?(self.native_type)
148
155
 
149
156
  context = Array(context)
@@ -162,18 +169,19 @@ module Attributor
162
169
  raise IncompatibleTypeError, context: context, value_type: value.class, type: self
163
170
  end
164
171
 
165
- self.from_hash(hash,context)
172
+ self.from_hash(hash,context, recurse: recurse)
166
173
  end
167
174
 
168
175
 
169
- def self.from_hash(hash,context)
176
+ def self.from_hash(hash,context, recurse: false)
170
177
  model = self.new
171
178
 
172
179
  self.attributes.each do |attribute_name, attribute|
180
+ # p [attribute_name, attribute]
173
181
  # OPTIMIZE: deleting the keys as we go is mucho faster, but also very risky
174
182
  # Note: use "load" vs. attribute assignment so we can propagate the right context down.
175
183
  sub_context = self.generate_subcontext(context,attribute_name)
176
- model.attributes[attribute_name] = attribute.load(hash[attribute_name] || hash[attribute_name.to_s], sub_context)
184
+ model.attributes[attribute_name] = attribute.load(hash[attribute_name] || hash[attribute_name.to_s], sub_context, recurse: recurse)
177
185
  end
178
186
 
179
187
  unknown_keys = hash.keys.collect {|k| k.to_sym} - self.attributes.keys
@@ -237,7 +245,7 @@ module Attributor
237
245
  @validating = false
238
246
  @dumping = false
239
247
  if data
240
- loaded = self.class.load( data )
248
+ loaded = self.class.load(data)
241
249
  @attributes = loaded.attributes
242
250
  else
243
251
  @attributes = ::Hash.new
@@ -8,7 +8,7 @@ module Attributor
8
8
 
9
9
 
10
10
  def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
11
- String(value)
11
+ value && String(value)
12
12
  rescue
13
13
  super
14
14
  end
@@ -0,0 +1,39 @@
1
+ require 'date'
2
+
3
+ module Attributor
4
+
5
+ class Time
6
+ include Type
7
+
8
+ def self.native_type
9
+ return ::Time
10
+ end
11
+
12
+ def self.example(context=nil, options: {})
13
+ return self.load(/[:time:]/.gen, context)
14
+ end
15
+
16
+ def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
17
+ return value if value.is_a?(self.native_type)
18
+ return nil if value.nil?
19
+
20
+ return value.to_time if value.respond_to?(:to_time)
21
+
22
+ case value
23
+ when ::Integer
24
+ return ::Time.at(value)
25
+ when ::String
26
+ begin
27
+ return ::Time.parse(value)
28
+ rescue ArgumentError => e
29
+ raise Attributor::DeserializationError, context: context, from: value.class, encoding: "Time" , value: value
30
+ end
31
+ else
32
+ raise CoercionError, context: context, from: value.class, to: self, value: value
33
+ end
34
+ end
35
+
36
+ end
37
+
38
+ end
39
+
@@ -1,3 +1,3 @@
1
1
  module Attributor
2
- VERSION = "2.2.1"
2
+ VERSION = "2.3.0"
3
3
  end
@@ -226,11 +226,6 @@ describe Attributor::Attribute do
226
226
  let(:context){ ['context'] }
227
227
  let(:value) { '1' }
228
228
 
229
- it 'does not call type.load for nil values' do
230
- type.should_not_receive(:load)
231
- attribute.load(nil)
232
- end
233
-
234
229
  it 'delegates to type.load' do
235
230
  type.should_receive(:load).with(value,context, {})
236
231
  attribute.load(value,context)
@@ -252,15 +247,13 @@ describe Attributor::Attribute do
252
247
  it { should == default_value}
253
248
  end
254
249
 
255
-
256
- context 'for a value that the type loads as nil' do
257
- let(:value) { "not nil"}
258
- before do
259
- type.should_receive(:load).and_return(nil)
260
- end
250
+ context 'for false' do
251
+ let(:type) { Attributor::Boolean }
252
+ let(:default_value) { false }
253
+ let(:value) { nil }
261
254
  it { should == default_value}
262
- end
263
255
 
256
+ end
264
257
  end
265
258
 
266
259
  context 'validating a value' do
@@ -0,0 +1,7 @@
1
+ class HashWithModel < Attributor::Hash
2
+ keys do
3
+ key :name, String, :default => "Turkey McDucken", :description => "Turducken name", :example => /[:name:]/
4
+ key :chicken, Chicken
5
+ end
6
+ end
7
+
@@ -33,7 +33,7 @@ end
33
33
 
34
34
  class Turducken < Attributor::Model
35
35
  attributes do
36
- attribute :name, String, :description => "Turducken name", :example => /[:name:]/
36
+ attribute :name, String, :default => "Turkey McDucken", :description => "Turducken name", :example => /[:name:]/
37
37
  attribute :chicken, Chicken
38
38
  attribute :duck, Duck
39
39
  attribute :turkey, Turkey, :description => "The turkey"
@@ -79,3 +79,5 @@ class Address < Attributor::Model
79
79
  attribute :person, Person, example: proc { |address, context| Person.example(context, address: address) }
80
80
  end
81
81
  end
82
+
83
+
@@ -0,0 +1,48 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+
3
+ describe Attributor::BigDecimal do
4
+ subject(:type) { Attributor::BigDecimal }
5
+
6
+ context '.native_type' do
7
+ its(:native_type) { should be(::BigDecimal) }
8
+ end
9
+
10
+ context '.example' do
11
+ its(:example) { should be_a(::BigDecimal) }
12
+ it do
13
+ ex = type.example
14
+ end
15
+ end
16
+
17
+ context '.load' do
18
+ let(:value) { nil }
19
+ it 'returns nil for nil' do
20
+ type.load(nil).should be(nil)
21
+ end
22
+
23
+ context 'for incoming Float values' do
24
+ it 'returns the incoming value' do
25
+ [0.0, -1.0, 1.0, 1e-10, 0.25135].each do |value|
26
+ type.load(value).should eq(value)
27
+ end
28
+ end
29
+ end
30
+
31
+ context 'for incoming Integer values' do
32
+ it 'should equal the incoming value' do
33
+ [0, -1, 1].each do |value|
34
+ type.load(value).should eq(value)
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'for incoming String values' do
40
+ it 'should equal the value' do
41
+ type.load('0').should eq(0)
42
+ type.load('100').should eq(100)
43
+ type.load('0.1').should eq(0.1)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -64,6 +64,10 @@ describe Attributor::Boolean do
64
64
 
65
65
  end
66
66
 
67
+ it 'returns nil for nil' do
68
+ type.load(nil).should be(nil)
69
+ end
70
+
67
71
  context 'that are not valid Booleans' do
68
72
  let(:context){ ['root','subattr'] }
69
73
  ['string', 2, 1.0, Class, Object.new].each do |value|
@@ -282,6 +282,17 @@ describe Attributor::Collection do
282
282
  value.all? { |element| member_type.valid_type?(element) }.should be_true
283
283
  end
284
284
  end
285
+
286
+ it "returns an Array with size specified" do
287
+ type.example(options: {size: 5}).size.should eq(5)
288
+ end
289
+
290
+ it "returns an Array with size within a range" do
291
+ 5.times do
292
+ type.example(options: {size: (2..4)}).size.should be_within(1).of(3)
293
+ end
294
+ end
295
+
285
296
  context "passing a non array context" do
286
297
  it 'still is handled correctly ' do
287
298
  expect{
@@ -289,6 +300,6 @@ describe Attributor::Collection do
289
300
  }.to_not raise_error
290
301
  end
291
302
  end
292
-
303
+
293
304
  end
294
305
  end
@@ -11,7 +11,38 @@ describe Attributor::CSV do
11
11
  it 'parses the value and returns an array with the right types' do
12
12
  csv.load(value).should eq(array)
13
13
  end
14
+ end
15
+
16
+ context '.example' do
17
+ let!(:example) { csv.example }
18
+ let!(:loaded_example) { csv.load(example) }
19
+
20
+ it 'generates a String example' do
21
+ example.should be_a(String)
22
+ end
23
+
24
+ it 'generates a comma-separated list of Integer values' do
25
+ loaded_example.should be_a(Array)
26
+ loaded_example.size.should be > 1
27
+ loaded_example.each { |e| e.should be_a(Integer) }
28
+ end
29
+ end
14
30
 
31
+ context '.dump' do
32
+ let!(:int_vals) { [1, 2, 3] }
33
+ let!(:str_vals) { (0..2).collect { /\w+/.gen} }
34
+
35
+ it 'dumps a String value' do
36
+ csv.dump(int_vals).should be_a(String)
37
+ end
38
+
39
+ it 'dumps a comma-separated list of Integers' do
40
+ csv.dump(int_vals).should eq(int_vals.join(','))
41
+ end
42
+
43
+ it 'dumps non-Integer values also' do
44
+ csv.dump(str_vals).should eq(str_vals.join(','))
45
+ end
15
46
  end
16
47
 
17
48
  end
@@ -0,0 +1,94 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+
3
+ describe Attributor::Date do
4
+
5
+ subject(:type) { Attributor::Date }
6
+
7
+ context '.native_type' do
8
+ its(:native_type) { should be(::Date) }
9
+ end
10
+
11
+ context '.example' do
12
+ its(:example) { should be_a(::Date) }
13
+ end
14
+
15
+ context '.load' do
16
+
17
+ it 'returns nil for nil' do
18
+ type.load(nil).should be(nil)
19
+ end
20
+
21
+ context 'for incoming objects' do
22
+
23
+ it "returns correct Date for Time objects" do
24
+ object = Time.now
25
+ loaded = type.load(object)
26
+ loaded.should be_a(::Date)
27
+ loaded.to_date.should == object.to_date
28
+ end
29
+
30
+ it "returns correct Date for DateTime objects" do
31
+ object = DateTime.now
32
+ loaded = type.load(object)
33
+ loaded.should be_a(::Date)
34
+ loaded.should be(object)
35
+ end
36
+
37
+ end
38
+
39
+ context 'for incoming strings' do
40
+
41
+ [
42
+ '2001-02-03T04:05:06+07:00',
43
+ 'Sat, 03 Feb 2001 04:05:06 GMT',
44
+ '20010203T040506+0700',
45
+ '2001-W05-6T04:05:06+07:00',
46
+ 'H13.02.03T04:05:06+07:00',
47
+ 'Sat, 3 Feb 2001 04:05:06 +0700',
48
+ '2013/08/23 00:39:55 +0000',
49
+ '2007-10-19T04:11:33Z',
50
+ '2001-02-03T04:05:06+07:00.123456', # custom format with microseconds
51
+ ].each do |value|
52
+
53
+ it "returns correct Date for #{value.inspect}" do
54
+ type.load(value).should == Date.parse(value)
55
+ end
56
+
57
+ end
58
+
59
+ [
60
+ 'Sat, 30 Feb 2001 04:05:06 GMT', # No such date exists
61
+ '2013/08/33 00:39:55 +0000',
62
+ '2007-10-33T04:11:33Z',
63
+ '2001-02-33T04:05:06+07:00.123456', # custom format with microseconds
64
+ ].each do |value|
65
+
66
+ it "raises Attributor::AttributorException for #{value.inspect}" do
67
+ expect {
68
+ type.load(value)
69
+ }.to raise_error(Attributor::DeserializationError, /Error deserializing a String using Date/)
70
+ end
71
+
72
+ end
73
+
74
+ [
75
+ '',
76
+ 'foobar',
77
+ 'Sat, 30 Feb 2001 04:05:06 FOOBAR', # No such date format exists
78
+ ].each do |value|
79
+
80
+ it "raises Attributor::AttributorException for #{value.inspect}" do
81
+ expect {
82
+ type.load(value)
83
+ }.to raise_error(Attributor::DeserializationError, /Error deserializing a String using Date/)
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+
92
+ end
93
+
94
+
@@ -14,8 +14,12 @@ describe Attributor::DateTime do
14
14
 
15
15
  context '.load' do
16
16
 
17
+ it 'returns nil for nil' do
18
+ type.load(nil).should be(nil)
19
+ end
20
+
17
21
  context 'for incoming objects' do
18
-
22
+
19
23
  it "returns correct DateTime for Time objects" do
20
24
  object = Time.now
21
25
  loaded = type.load(object)
@@ -35,15 +39,15 @@ describe Attributor::DateTime do
35
39
  context 'for incoming strings' do
36
40
 
37
41
  [
38
- '2001-02-03T04:05:06+07:00',
39
- 'Sat, 03 Feb 2001 04:05:06 GMT',
40
- '20010203T040506+0700',
41
- '2001-W05-6T04:05:06+07:00',
42
- 'H13.02.03T04:05:06+07:00',
43
- 'Sat, 3 Feb 2001 04:05:06 +0700',
44
- '2013/08/23 00:39:55 +0000', # Right API 1.5
45
- '2007-10-19T04:11:33Z', # Right API 1.0
46
- '2001-02-03T04:05:06+07:00.123456', # custom format with microseconds
42
+ '2001-02-03T04:05:06+07:00',
43
+ 'Sat, 03 Feb 2001 04:05:06 GMT',
44
+ '20010203T040506+0700',
45
+ '2001-W05-6T04:05:06+07:00',
46
+ 'H13.02.03T04:05:06+07:00',
47
+ 'Sat, 3 Feb 2001 04:05:06 +0700',
48
+ '2013/08/23 00:39:55 +0000',
49
+ '2007-10-19T04:11:33Z',
50
+ '2001-02-03T04:05:06+07:00.123456', # custom format with microseconds
47
51
  ].each do |value|
48
52
 
49
53
  it "returns correct DateTime for #{value.inspect}" do
@@ -53,10 +57,10 @@ describe Attributor::DateTime do
53
57
  end
54
58
 
55
59
  [
56
- 'Sat, 30 Feb 2001 04:05:06 GMT', # No such date exists
57
- '2013/08/33 00:39:55 +0000', # Right API 1.5
58
- '2007-10-33T04:11:33Z', # Right API 1.0
59
- '2001-02-33T04:05:06+07:00.123456', # custom format with microseconds
60
+ 'Sat, 30 Feb 2001 04:05:06 GMT', # No such date exists
61
+ '2013/08/33 00:39:55 +0000',
62
+ '2007-10-33T04:11:33Z',
63
+ '2001-02-33T04:05:06+07:00.123456', # custom format with microseconds
60
64
  ].each do |value|
61
65
 
62
66
  it "raises Attributor::AttributorException for #{value.inspect}" do
@@ -68,9 +72,9 @@ describe Attributor::DateTime do
68
72
  end
69
73
 
70
74
  [
71
- '',
72
- 'foobar',
73
- 'Sat, 30 Feb 2001 04:05:06 FOOBAR', # No such date format exists
75
+ '',
76
+ 'foobar',
77
+ 'Sat, 30 Feb 2001 04:05:06 FOOBAR', # No such date format exists
74
78
  ].each do |value|
75
79
 
76
80
  it "raises Attributor::AttributorException for #{value.inspect}" do