attributor 2.2.1 → 2.3.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.
@@ -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