attributor 3.0 → 3.0.1
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 +9 -0
- data/lib/attributor.rb +6 -0
- data/lib/attributor/attribute.rb +8 -7
- data/lib/attributor/example_mixin.rb +3 -2
- data/lib/attributor/type.rb +1 -1
- data/lib/attributor/types/collection.rb +15 -13
- data/lib/attributor/types/hash.rb +25 -3
- data/lib/attributor/types/model.rb +2 -2
- data/lib/attributor/types/tempfile.rb +6 -2
- data/lib/attributor/version.rb +1 -1
- data/spec/attribute_spec.rb +5 -6
- data/spec/attributor_spec.rb +21 -0
- data/spec/types/collection_spec.rb +4 -2
- 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: bff0810c57451a4f93b1f23d7bbdc947757ee3ab
|
4
|
+
data.tar.gz: 4017dd9949a54d8d6e14aa5c11aff80aac0f0358
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a8f0a4e9ac2a34d486c5ccd83da33cc8ba073e91ae82c747d01de08652e3b4f7705fc5efad274edafa2f55599b3b7f479a63552ae596854d4e8ba295513dd596
|
7
|
+
data.tar.gz: 2eb0699f1d50de06159e9cd65a8e824804c1be0175384f597cf31c18dc7b438beca565fbe33d2b80a23ffff655d2fb7255d3d5ff28d2d415db12ee5a64fe5cc0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,14 @@
|
|
1
1
|
# Attributor Changelog
|
2
2
|
|
3
|
+
## next
|
4
|
+
|
5
|
+
|
6
|
+
## 3.0.1
|
7
|
+
|
8
|
+
* Fixed bug with example Hashes where `[]` with a key not in the hash would throw a `NoMethodError`.
|
9
|
+
* Fixed bug in `Hash#get` for Hashes without predefined keys. It would throw an error if given a key not present in the hash's contents.
|
10
|
+
|
11
|
+
|
3
12
|
## 3.0.0
|
4
13
|
|
5
14
|
* Small enhancements on `describe` for types
|
data/lib/attributor.rb
CHANGED
@@ -44,6 +44,12 @@ module Attributor
|
|
44
44
|
klass
|
45
45
|
end
|
46
46
|
|
47
|
+
def self.type_name(type)
|
48
|
+
return self.type_name(type.class) unless type.kind_of?(Class)
|
49
|
+
|
50
|
+
type.ancestors.find { |k| k.name && !k.name.empty? }.name
|
51
|
+
end
|
52
|
+
|
47
53
|
def self.humanize_context( context )
|
48
54
|
return "" unless context
|
49
55
|
|
data/lib/attributor/attribute.rb
CHANGED
@@ -42,15 +42,15 @@ module Attributor
|
|
42
42
|
end
|
43
43
|
|
44
44
|
|
45
|
-
|
46
|
-
|
45
|
+
def parse(value, context=Attributor::DEFAULT_ROOT_CONTEXT)
|
46
|
+
object = self.load(value,context)
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
48
|
+
errors = self.validate(object,context)
|
49
|
+
[ object, errors ]
|
50
|
+
end
|
51
51
|
|
52
52
|
|
53
|
-
def load(value, context=Attributor::DEFAULT_ROOT_CONTEXT,
|
53
|
+
def load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
54
54
|
value = type.load(value,context,**options)
|
55
55
|
|
56
56
|
if value.nil? && self.options.has_key?(:default)
|
@@ -98,7 +98,8 @@ module Attributor
|
|
98
98
|
|
99
99
|
TOP_LEVEL_OPTIONS = [ :description, :values, :default, :example, :required, :required_if, :custom_data ]
|
100
100
|
INTERNAL_OPTIONS = [:dsl_compiler,:dsl_compiler_options] # Options we don't want to expose when describing attributes
|
101
|
-
|
101
|
+
|
102
|
+
def describe(shallow=true, example: nil)
|
102
103
|
description = { }
|
103
104
|
# Clone the common options
|
104
105
|
TOP_LEVEL_OPTIONS.each do |option_name|
|
data/lib/attributor/type.rb
CHANGED
@@ -106,7 +106,7 @@ module Attributor
|
|
106
106
|
|
107
107
|
# Default describe for simple types...only their name (stripping the base attributor module)
|
108
108
|
def describe(root=false, example: nil)
|
109
|
-
type_name = self
|
109
|
+
type_name = Attributor.type_name(self)
|
110
110
|
hash = {
|
111
111
|
name: type_name.gsub(Attributor::MODULE_PREFIX_REGEX, ''),
|
112
112
|
family: self.family,
|
@@ -53,6 +53,7 @@ module Attributor
|
|
53
53
|
def self.member_attribute
|
54
54
|
@member_attribute ||= begin
|
55
55
|
self.construct(nil,{})
|
56
|
+
|
56
57
|
@member_attribute
|
57
58
|
end
|
58
59
|
end
|
@@ -153,22 +154,15 @@ module Attributor
|
|
153
154
|
:ok
|
154
155
|
end
|
155
156
|
|
156
|
-
# @param
|
157
|
-
def self.validate(
|
157
|
+
# @param object [Collection] Collection instance to validate.
|
158
|
+
def self.validate(object, context=Attributor::DEFAULT_ROOT_CONTEXT, attribute=nil)
|
159
|
+
context = [context] if context.is_a? ::String
|
158
160
|
|
159
|
-
unless
|
160
|
-
|
161
|
-
"Collection.of(#{self.member_type})"
|
162
|
-
else
|
163
|
-
self
|
164
|
-
end
|
165
|
-
raise Attributor::IncompatibleTypeError, context: context, value_type: values.class, type: descriptive_type
|
161
|
+
unless object.kind_of?(self)
|
162
|
+
raise ArgumentError, "#{self.name} can not validate object of type #{object.class.name} for #{Attributor.humanize_context(context)}."
|
166
163
|
end
|
167
164
|
|
168
|
-
|
169
|
-
subcontext = context + ["at(#{i})"]
|
170
|
-
self.member_attribute.validate(value, subcontext)
|
171
|
-
end.flatten.compact
|
165
|
+
object.validate(context)
|
172
166
|
end
|
173
167
|
|
174
168
|
def self.validate_options( value, context, attribute )
|
@@ -177,6 +171,14 @@ module Attributor
|
|
177
171
|
end
|
178
172
|
|
179
173
|
|
174
|
+
def validate(context=Attributor::DEFAULT_ROOT_CONTEXT)
|
175
|
+
self.each_with_index.collect do |value, i|
|
176
|
+
subcontext = context + ["at(#{i})"]
|
177
|
+
self.class.member_attribute.validate(value, subcontext)
|
178
|
+
end.flatten.compact
|
179
|
+
end
|
180
|
+
|
181
|
+
|
180
182
|
def dump(**opts)
|
181
183
|
self.collect { |value| self.class.member_attribute.dump(value,opts) }
|
182
184
|
end
|
@@ -245,7 +245,7 @@ module Attributor
|
|
245
245
|
end
|
246
246
|
|
247
247
|
|
248
|
-
def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, recurse: false,
|
248
|
+
def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, recurse: false, **options)
|
249
249
|
context = Array(context)
|
250
250
|
|
251
251
|
if value.nil?
|
@@ -288,6 +288,24 @@ module Attributor
|
|
288
288
|
def get(key, context: self.generate_subcontext(Attributor::DEFAULT_ROOT_CONTEXT,key))
|
289
289
|
key = self.class.key_attribute.load(key, context)
|
290
290
|
|
291
|
+
if self.class.keys.empty?
|
292
|
+
if @contents.key? key
|
293
|
+
value = @contents[key]
|
294
|
+
loaded_value = value_attribute.load(value, context)
|
295
|
+
return self[key] = loaded_value
|
296
|
+
else
|
297
|
+
if self.class.options[:case_insensitive_load]
|
298
|
+
key = key.downcase
|
299
|
+
@contents.each do |k,v|
|
300
|
+
if key == k.downcase
|
301
|
+
return self.get(key, context: context)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
return nil
|
307
|
+
end
|
308
|
+
|
291
309
|
value = @contents[key]
|
292
310
|
|
293
311
|
# FIXME: getting an unset value here should not force it in the hash
|
@@ -318,6 +336,7 @@ module Attributor
|
|
318
336
|
end
|
319
337
|
end
|
320
338
|
|
339
|
+
|
321
340
|
raise AttributorException, "Unknown key received: #{key.inspect} for #{Attributor.humanize_context(context)}"
|
322
341
|
end
|
323
342
|
|
@@ -325,6 +344,10 @@ module Attributor
|
|
325
344
|
def set(key, value, context: self.generate_subcontext(Attributor::DEFAULT_ROOT_CONTEXT,key), recurse: false)
|
326
345
|
key = self.class.key_attribute.load(key, context)
|
327
346
|
|
347
|
+
if self.class.keys.empty?
|
348
|
+
return self[key] = self.class.value_attribute.load(value, context)
|
349
|
+
end
|
350
|
+
|
328
351
|
if (attribute = self.class.keys[key])
|
329
352
|
return self[key] = attribute.load(value, context, recurse: recurse)
|
330
353
|
end
|
@@ -541,10 +564,9 @@ module Attributor
|
|
541
564
|
|
542
565
|
def dump(**opts)
|
543
566
|
return CIRCULAR_REFERENCE_MARKER if @dumping
|
544
|
-
|
545
567
|
@dumping = true
|
546
568
|
|
547
|
-
|
569
|
+
contents.each_with_object({}) do |(k,v),hash|
|
548
570
|
k = self.key_attribute.dump(k,opts)
|
549
571
|
|
550
572
|
if (attribute_for_value = self.class.keys[k])
|
@@ -177,7 +177,7 @@ module Attributor
|
|
177
177
|
return CIRCULAR_REFERENCE_MARKER if @dumping
|
178
178
|
@dumping = true
|
179
179
|
|
180
|
-
self.attributes.each_with_object({}) do |(name, value),
|
180
|
+
self.attributes.each_with_object({}) do |(name, value), hash|
|
181
181
|
attribute = self.class.attributes[name]
|
182
182
|
|
183
183
|
# skip dumping undefined attributes
|
@@ -186,7 +186,7 @@ module Attributor
|
|
186
186
|
next
|
187
187
|
end
|
188
188
|
|
189
|
-
|
189
|
+
hash[name.to_sym] = attribute.dump(value, context: context + [name] )
|
190
190
|
end
|
191
191
|
ensure
|
192
192
|
@dumping = false
|
@@ -9,11 +9,15 @@ module Attributor
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def self.example(context=Attributor::DEFAULT_ROOT_CONTEXT, options:{})
|
12
|
-
::Tempfile.new(Attributor.humanize_context(context))
|
12
|
+
file = ::Tempfile.new(Attributor.humanize_context(context))
|
13
|
+
file.write /[:sentence:]/.gen
|
14
|
+
file.write '.'
|
15
|
+
file.rewind
|
16
|
+
file
|
13
17
|
end
|
14
18
|
|
15
19
|
def self.dump(value, **opts)
|
16
|
-
value && value.
|
20
|
+
value && value.read
|
17
21
|
end
|
18
22
|
|
19
23
|
def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
|
data/lib/attributor/version.rb
CHANGED
data/spec/attribute_spec.rb
CHANGED
@@ -690,13 +690,12 @@ describe Attributor::Attribute do
|
|
690
690
|
end
|
691
691
|
|
692
692
|
it 'validates' do
|
693
|
-
|
694
|
-
errors.
|
695
|
-
errors[0].should =~ /of the wrong type/
|
696
|
-
errors[1].should =~ /value \(12\) is larger/
|
697
|
-
end
|
698
|
-
|
693
|
+
object = attribute.load(values)
|
694
|
+
errors = attribute.validate(object)
|
699
695
|
|
696
|
+
errors.should have(1).item
|
697
|
+
errors[0].should =~ /value \(12\) is larger/
|
698
|
+
end
|
700
699
|
end
|
701
700
|
|
702
701
|
|
data/spec/attributor_spec.rb
CHANGED
@@ -39,4 +39,25 @@ describe Attributor do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
end
|
42
|
+
|
43
|
+
|
44
|
+
context '.type_name' do
|
45
|
+
it 'accepts arbtirary classes' do
|
46
|
+
Attributor.type_name(File).should eq 'File'
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'accepts instances' do
|
50
|
+
Attributor.type_name('a string').should eq 'String'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'accepts instances of anonymous types' do
|
54
|
+
type = Class.new(Attributor::Struct)
|
55
|
+
Attributor.type_name(type).should eq 'Attributor::Struct'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'accepts Attributor types' do
|
59
|
+
Attributor.type_name(Attributor::String).should eq 'Attributor::String'
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
42
63
|
end
|
@@ -253,6 +253,8 @@ describe Attributor::Collection do
|
|
253
253
|
let(:collection_members) { [1, 2, 'three'] }
|
254
254
|
let(:expected_errors) { ["error 1", "error 2", "error 3"]}
|
255
255
|
|
256
|
+
let(:value) { type.load(collection_members) }
|
257
|
+
|
256
258
|
before do
|
257
259
|
collection_members.zip(expected_errors).each do |member, expected_error|
|
258
260
|
type.member_attribute.should_receive(:validate).
|
@@ -262,7 +264,7 @@ describe Attributor::Collection do
|
|
262
264
|
end
|
263
265
|
|
264
266
|
it 'validates members' do
|
265
|
-
type.validate(
|
267
|
+
type.validate(value).should =~ expected_errors
|
266
268
|
end
|
267
269
|
end
|
268
270
|
context 'invalid incoming types' do
|
@@ -270,7 +272,7 @@ describe Attributor::Collection do
|
|
270
272
|
it 'raise an exception' do
|
271
273
|
expect {
|
272
274
|
type.validate('invalid_value')
|
273
|
-
}.to raise_error(
|
275
|
+
}.to raise_error(ArgumentError, /can not validate object of type String/)
|
274
276
|
end
|
275
277
|
end
|
276
278
|
end
|
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:
|
4
|
+
version: 3.0.1
|
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: 2015-
|
12
|
+
date: 2015-07-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: hashie
|