attributor 3.0 → 3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|