attributor 2.4.0 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/attributor.gemspec +0 -1
- data/lib/attributor/attribute.rb +30 -2
- data/lib/attributor/type.rb +9 -1
- data/lib/attributor/types/date.rb +4 -0
- data/lib/attributor/types/date_time.rb +24 -20
- data/lib/attributor/types/hash.rb +2 -0
- data/lib/attributor/types/model.rb +22 -0
- data/lib/attributor/types/time.rb +5 -1
- data/lib/attributor/version.rb +1 -1
- data/spec/attribute_spec.rb +36 -3
- data/spec/type_spec.rb +16 -3
- data/spec/types/date_spec.rb +9 -0
- data/spec/types/date_time_spec.rb +9 -0
- data/spec/types/hash_spec.rb +7 -7
- data/spec/types/model_spec.rb +16 -0
- data/spec/types/time_spec.rb +9 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b7e1ce2db7e093c7a3073db2cea1d45b861f015
|
4
|
+
data.tar.gz: f3623a6d5e3b313b9493cba77a6e7a7b250d1de5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 625ac84d1636b6d21044c69d8be170bae210b28f302c2ed78de5b3130e1d82e760ee65c68a6b9f0c614e06b8a2a4770ab26da92a6b5917d847a1a02a73bdb522
|
7
|
+
data.tar.gz: f7c4324913a80c92191c9bc6f46ced3d5f7e9f01758faea751c79254b24e547f68b88ce836c446523cd759e2c35d4ca04778b7c033787878b5f75e88b77d7046
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,20 @@
|
|
1
1
|
Attributor Changelog
|
2
2
|
============================
|
3
3
|
|
4
|
+
next
|
5
|
+
----
|
6
|
+
|
7
|
+
2.5.0
|
8
|
+
----
|
9
|
+
|
10
|
+
* Partial support for defining `:default` values through Procs.
|
11
|
+
* Note: this is only "partially" supported the `parent` argument of the Proc will NOT contain the correct attribute parent yet. It will contain a fake class, that will loudly complain about any attempt to use any of its methods.
|
12
|
+
* Fixed `Model.example` to properly handle the case when no attributes are defined on the class.
|
13
|
+
* `Model#dump` now issues a warning if its contents have keys for attributes not present on the class. The unknown contents are not dumped.
|
14
|
+
* `Hash.load` now supports loading any value that responds to `to_hash`.
|
15
|
+
* `Time`, `DateTime`, and `Date` now all return ISO 8601 formatted values from `.dump` (via calling `iso8601` on the value).
|
16
|
+
* Added `Type.id`, a unique value based on the type's class name.
|
17
|
+
|
4
18
|
2.4.0
|
5
19
|
------
|
6
20
|
|
data/attributor.gemspec
CHANGED
@@ -8,7 +8,6 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.name = "attributor"
|
9
9
|
spec.version = Attributor::VERSION
|
10
10
|
spec.authors = ["Josep M. Blanquer","Dane Jensen"]
|
11
|
-
spec.date = "2014-08-15"
|
12
11
|
spec.summary = "A powerful attribute and type management library for Ruby"
|
13
12
|
spec.email = ["blanquer@gmail.com","dane.jensen@gmail.com"]
|
14
13
|
|
data/lib/attributor/attribute.rb
CHANGED
@@ -2,6 +2,19 @@
|
|
2
2
|
|
3
3
|
module Attributor
|
4
4
|
|
5
|
+
class FakeParent < ::BasicObject
|
6
|
+
|
7
|
+
def method_missing(name, *args)
|
8
|
+
::Kernel.warn "Warning, you have tried to access the '#{name}' method of the 'parent' argument of a Proc-defined :default values." +
|
9
|
+
"Those Procs should completely ignore the 'parent' attribute for the moment as it will be set to an " +
|
10
|
+
"instance of a useless class (until the framework can provide such functionality)"
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def class
|
15
|
+
FakeParent
|
16
|
+
end
|
17
|
+
end
|
5
18
|
# It is the abstract base class to hold an attribute, both a leaf and a container (hash/Array...)
|
6
19
|
# TODO: should this be a mixin since it is an abstract class?
|
7
20
|
class Attribute
|
@@ -40,8 +53,23 @@ module Attributor
|
|
40
53
|
def load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
41
54
|
value = type.load(value,context,**options)
|
42
55
|
|
43
|
-
if value.nil?
|
44
|
-
|
56
|
+
if value.nil? && self.options.has_key?(:default)
|
57
|
+
defined_val = self.options[:default]
|
58
|
+
val = case defined_val
|
59
|
+
when ::Proc
|
60
|
+
fake_parent = FakeParent.new
|
61
|
+
# TODO: we can only support "context" as a parameter to the proc for now, since we don't have the parent...
|
62
|
+
if defined_val.arity == 2
|
63
|
+
defined_val.call(fake_parent, context)
|
64
|
+
elsif defined_val.arity == 1
|
65
|
+
defined_val.call(fake_parent)
|
66
|
+
else
|
67
|
+
defined_val.call
|
68
|
+
end
|
69
|
+
else
|
70
|
+
defined_val
|
71
|
+
end
|
72
|
+
value = val #Need to load?
|
45
73
|
end
|
46
74
|
|
47
75
|
value
|
data/lib/attributor/type.rb
CHANGED
@@ -107,9 +107,17 @@ module Attributor
|
|
107
107
|
# Default describe for simple types...only their name (stripping the base attributor module)
|
108
108
|
def describe(root=false)
|
109
109
|
type_name = self.ancestors.find { |k| k.name && !k.name.empty? }.name
|
110
|
-
{
|
110
|
+
{
|
111
|
+
name: type_name.gsub(Attributor::MODULE_PREFIX_REGEX, ''),
|
112
|
+
id: self.id
|
113
|
+
}
|
111
114
|
end
|
112
115
|
|
116
|
+
def id
|
117
|
+
return nil if self.name.nil?
|
118
|
+
self.name.gsub('::'.freeze,'-'.freeze)
|
119
|
+
end
|
120
|
+
|
113
121
|
end
|
114
122
|
end
|
115
123
|
end
|
@@ -5,32 +5,36 @@ require 'date'
|
|
5
5
|
|
6
6
|
module Attributor
|
7
7
|
|
8
|
-
|
9
|
-
|
8
|
+
class DateTime
|
9
|
+
include Type
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
11
|
+
def self.native_type
|
12
|
+
return ::DateTime
|
13
|
+
end
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def self.example(context=nil, options: {})
|
16
|
+
return self.load(/[:date:]/.gen, context)
|
17
|
+
end
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
end
|
19
|
+
def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
20
|
+
# We assume that if the value is already in the right type, we've decoded it already
|
21
|
+
return value if value.is_a?(self.native_type)
|
22
|
+
return value.to_datetime if value.respond_to?(:to_datetime)
|
23
|
+
return nil unless value.is_a?(::String)
|
24
|
+
# TODO: we should be able to convert not only from String but Time...etc
|
25
|
+
# Else, we'll decode it from String.
|
26
|
+
begin
|
27
|
+
return ::DateTime.parse(value)
|
28
|
+
rescue ArgumentError => e
|
29
|
+
raise Attributor::DeserializationError, context: context, from: value.class, encoding: "DateTime" , value: value
|
31
30
|
end
|
31
|
+
end
|
32
32
|
|
33
|
+
def self.dump(value,**opts)
|
34
|
+
value.iso8601
|
33
35
|
end
|
34
36
|
|
37
|
+
|
35
38
|
end
|
36
39
|
|
40
|
+
end
|
@@ -230,6 +230,8 @@ module Attributor
|
|
230
230
|
loaded_value = value
|
231
231
|
elsif value.is_a?(::String)
|
232
232
|
loaded_value = decode_json(value,context)
|
233
|
+
elsif value.respond_to?(:to_hash)
|
234
|
+
loaded_value = value.to_hash
|
233
235
|
else
|
234
236
|
raise Attributor::IncompatibleTypeError, context: context, value_type: value.class, type: self
|
235
237
|
end
|
@@ -92,6 +92,21 @@ module Attributor
|
|
92
92
|
context + [subname]
|
93
93
|
end
|
94
94
|
|
95
|
+
def self.example(context=nil, **values)
|
96
|
+
context ||= ["#{self.name || 'Struct'}-#{rand(10000000)}"]
|
97
|
+
context = Array(context)
|
98
|
+
|
99
|
+
if self.keys.any?
|
100
|
+
result = self.new
|
101
|
+
result.extend(ExampleMixin)
|
102
|
+
|
103
|
+
result.lazy_attributes = self.example_contents(context, result, values)
|
104
|
+
else
|
105
|
+
result = self.new
|
106
|
+
end
|
107
|
+
result
|
108
|
+
end
|
109
|
+
|
95
110
|
def initialize(data = nil)
|
96
111
|
if data
|
97
112
|
loaded = self.class.load(data)
|
@@ -161,6 +176,13 @@ module Attributor
|
|
161
176
|
|
162
177
|
self.attributes.each_with_object({}) do |(name, value), result|
|
163
178
|
attribute = self.class.attributes[name]
|
179
|
+
|
180
|
+
# skip dumping undefined attributes
|
181
|
+
unless attribute
|
182
|
+
warn "WARNING: Trying to dump unknown attribute: #{name.inspect} with context: #{context.inspect}"
|
183
|
+
next
|
184
|
+
end
|
185
|
+
|
164
186
|
result[name.to_sym] = attribute.dump(value, context: context + [name] )
|
165
187
|
end
|
166
188
|
ensure
|
data/lib/attributor/version.rb
CHANGED
data/spec/attribute_spec.rb
CHANGED
@@ -43,7 +43,7 @@ describe Attributor::Attribute do
|
|
43
43
|
context 'describe' do
|
44
44
|
let(:attribute_options) { {:required => true, :values => ["one"], :description => "something", :min => 0} }
|
45
45
|
let(:expected) do
|
46
|
-
h = {:
|
46
|
+
h = {type: {name: type.name, id: type.id}}
|
47
47
|
common = attribute_options.select{|k,v| Attributor::Attribute::TOP_LEVEL_OPTIONS.include? k }
|
48
48
|
h.merge!( common )
|
49
49
|
h[:options] = {:min => 0 }
|
@@ -237,23 +237,56 @@ describe Attributor::Attribute do
|
|
237
237
|
end
|
238
238
|
|
239
239
|
context 'applying default values' do
|
240
|
+
let(:value) { nil }
|
240
241
|
let(:default_value) { "default value" }
|
241
242
|
let(:attribute_options) { {:default => default_value} }
|
242
243
|
|
243
244
|
subject(:result) { attribute.load(value) }
|
244
245
|
|
245
246
|
context 'for nil' do
|
246
|
-
let(:value) { nil }
|
247
247
|
it { should == default_value}
|
248
248
|
end
|
249
249
|
|
250
250
|
context 'for false' do
|
251
251
|
let(:type) { Attributor::Boolean }
|
252
252
|
let(:default_value) { false }
|
253
|
-
let(:value) { nil }
|
254
253
|
it { should == default_value}
|
255
254
|
|
256
255
|
end
|
256
|
+
|
257
|
+
context 'for a Proc-based default value' do
|
258
|
+
let(:context){ ["$"] }
|
259
|
+
subject(:result){ attribute.load(value,context) }
|
260
|
+
|
261
|
+
|
262
|
+
context 'with no arguments arguments' do
|
263
|
+
let(:default_value) { proc { "no_params" } }
|
264
|
+
it { should == default_value.call }
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'with 1 argument (the parent)' do
|
268
|
+
let(:default_value) { proc {|parent| "parent is fake: #{parent.class}" } }
|
269
|
+
it { should == "parent is fake: Attributor::FakeParent" }
|
270
|
+
end
|
271
|
+
|
272
|
+
context 'with 2 argument (the parent and the contents)' do
|
273
|
+
let(:default_value) { proc {|parent,context| "parent is fake: #{parent.class} and context is: #{context}" } }
|
274
|
+
it { should == "parent is fake: Attributor::FakeParent and context is: [\"$\"]"}
|
275
|
+
end
|
276
|
+
|
277
|
+
context 'which attempts to use the parent (which is not supported for the moment)' do
|
278
|
+
let(:default_value) { proc {|parent| "any parent method should spit out warning: [#{parent.something}]" } }
|
279
|
+
it "should output a warning" do
|
280
|
+
begin
|
281
|
+
old_verbose, $VERBOSE = $VERBOSE, nil
|
282
|
+
Kernel.should_receive(:warn).and_call_original
|
283
|
+
attribute.load(value,context).should == "any parent method should spit out warning: []"
|
284
|
+
ensure
|
285
|
+
$VERBOSE = old_verbose
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
257
290
|
end
|
258
291
|
|
259
292
|
context 'validating a value' do
|
data/spec/type_spec.rb
CHANGED
@@ -3,7 +3,6 @@ require File.join(File.dirname(__FILE__), 'spec_helper.rb')
|
|
3
3
|
|
4
4
|
describe Attributor::Type do
|
5
5
|
|
6
|
-
|
7
6
|
subject(:test_type) { AttributeType }
|
8
7
|
|
9
8
|
let(:attribute_options) { Hash.new }
|
@@ -17,6 +16,7 @@ describe Attributor::Type do
|
|
17
16
|
|
18
17
|
|
19
18
|
its(:native_type) { should be(::String) }
|
19
|
+
its(:id) { should eq('AttributeType')}
|
20
20
|
|
21
21
|
|
22
22
|
context 'load' do
|
@@ -123,12 +123,25 @@ describe Attributor::Type do
|
|
123
123
|
|
124
124
|
end
|
125
125
|
|
126
|
+
context 'id' do
|
127
|
+
it 'works for built-in types' do
|
128
|
+
Attributor::String.id.should eq('Attributor-String')
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'returns nil for anonymous types' do
|
132
|
+
type = Class.new(Attributor::Model)
|
133
|
+
type.id.should eq(nil)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
126
137
|
context 'describe' do
|
127
138
|
subject(:description) { test_type.describe }
|
128
139
|
it 'outputs the type name' do
|
129
|
-
description[:name].should
|
140
|
+
description[:name].should eq(test_type.name)
|
141
|
+
end
|
142
|
+
it 'outputs the type id' do
|
143
|
+
description[:id].should eq(test_type.name)
|
130
144
|
end
|
131
|
-
|
132
145
|
end
|
133
146
|
|
134
147
|
end
|
data/spec/types/date_spec.rb
CHANGED
@@ -12,6 +12,15 @@ describe Attributor::Date do
|
|
12
12
|
its(:example) { should be_a(::Date) }
|
13
13
|
end
|
14
14
|
|
15
|
+
context '.dump' do
|
16
|
+
let(:example) { type.example}
|
17
|
+
subject(:value) { type.dump(example) }
|
18
|
+
it 'is formatted correctly' do
|
19
|
+
value.should match(/\d{4}-\d{2}-\d{2}T00:00:00\+00:00/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
15
24
|
context '.load' do
|
16
25
|
|
17
26
|
it 'returns nil for nil' do
|
@@ -12,6 +12,15 @@ describe Attributor::DateTime do
|
|
12
12
|
its(:example) { should be_a(::DateTime) }
|
13
13
|
end
|
14
14
|
|
15
|
+
context '.dump' do
|
16
|
+
let(:example) { type.example}
|
17
|
+
subject(:value) { type.dump(example) }
|
18
|
+
it 'is formatted correctly' do
|
19
|
+
value.should match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\+-]\d{2}:\d{2}/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
15
24
|
context '.load' do
|
16
25
|
|
17
26
|
it 'returns nil for nil' do
|
data/spec/types/hash_spec.rb
CHANGED
@@ -343,8 +343,8 @@ describe Attributor::Hash do
|
|
343
343
|
context 'for hashes with key and value types' do
|
344
344
|
it 'describes the type correctly' do
|
345
345
|
description[:name].should eq('Hash')
|
346
|
-
description[:key].should eq(type:{name: 'Object'})
|
347
|
-
description[:value].should eq(type:{name: 'Object'})
|
346
|
+
description[:key].should eq(type:{name: 'Object', id: 'Attributor-Object'})
|
347
|
+
description[:value].should eq(type:{name: 'Object', id: 'Attributor-Object'})
|
348
348
|
end
|
349
349
|
end
|
350
350
|
|
@@ -362,15 +362,15 @@ describe Attributor::Hash do
|
|
362
362
|
|
363
363
|
it 'describes the type correctly' do
|
364
364
|
description[:name].should eq('Hash')
|
365
|
-
description[:key].should eq(type:{name: 'String'})
|
365
|
+
description[:key].should eq(type:{name: 'String', id: 'Attributor-String'})
|
366
366
|
description.should_not have_key(:value)
|
367
367
|
|
368
368
|
keys = description[:keys]
|
369
369
|
|
370
|
-
keys['a string'].should eq(type: {name: 'String'} )
|
371
|
-
keys['1'].should eq(type: {name: 'Integer'}, options: {min: 1, max: 20} )
|
372
|
-
keys['some_date'].should eq(type: {name: 'DateTime' })
|
373
|
-
keys['defaulted'].should eq(type: {name: 'String'}, default: 'default value')
|
370
|
+
keys['a string'].should eq(type: {name: 'String', id: 'Attributor-String'} )
|
371
|
+
keys['1'].should eq(type: {name: 'Integer', id: 'Attributor-Integer'}, options: {min: 1, max: 20} )
|
372
|
+
keys['some_date'].should eq(type: {name: 'DateTime', id: 'Attributor-DateTime'})
|
373
|
+
keys['defaulted'].should eq(type: {name: 'String', id: 'Attributor-String'}, default: 'default value')
|
374
374
|
end
|
375
375
|
end
|
376
376
|
end
|
data/spec/types/model_spec.rb
CHANGED
@@ -416,6 +416,22 @@ describe Attributor::Model do
|
|
416
416
|
|
417
417
|
end
|
418
418
|
|
419
|
+
context 'with no defined attributes' do
|
420
|
+
let(:model_class) do
|
421
|
+
Class.new(Attributor::Model) do
|
422
|
+
attributes do
|
423
|
+
end
|
424
|
+
end
|
425
|
+
end
|
419
426
|
|
427
|
+
subject(:example) { model_class.example }
|
428
|
+
|
429
|
+
its(:attributes) { should be_empty }
|
430
|
+
|
431
|
+
it 'dumps as an empty hash' do
|
432
|
+
example.dump.should eq({})
|
433
|
+
end
|
434
|
+
|
435
|
+
end
|
420
436
|
|
421
437
|
end
|
data/spec/types/time_spec.rb
CHANGED
@@ -12,6 +12,15 @@ describe Attributor::Time do
|
|
12
12
|
its(:example) { should be_a(::Time) }
|
13
13
|
end
|
14
14
|
|
15
|
+
context '.dump' do
|
16
|
+
let(:example) { type.example}
|
17
|
+
subject(:value) { type.dump(example) }
|
18
|
+
it 'is formatted correctly' do
|
19
|
+
value.should match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[\+-]\d{2}:\d{2}/)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
15
24
|
context '.load' do
|
16
25
|
|
17
26
|
it 'returns nil for nil' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: attributor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hashie
|