attributor 2.4.0 → 2.5.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.
- 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
|