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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a9b60ce0b5ac69797dca8bc43ce9164e8a666eeb
4
- data.tar.gz: d87a417b41105b3ed4ae10098bd14542d88d7f96
3
+ metadata.gz: bff0810c57451a4f93b1f23d7bbdc947757ee3ab
4
+ data.tar.gz: 4017dd9949a54d8d6e14aa5c11aff80aac0f0358
5
5
  SHA512:
6
- metadata.gz: 660346014295ff874c70f0cc79931b98c109c0034e1b235f516108aa5857fa71688a2a2fcd7aab8c9636c91b87a64d1b82d09ae1a7d247b0bf7a23cf232a80d2
7
- data.tar.gz: 174c4d2859d2ad0611c55b3c16c70eaa80440fcdee5308fe84a917192d624383e65bfae19c5bd593732ac59ba0d13d908e4c018406f0256fe0f1c30864911fc1
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
 
@@ -42,15 +42,15 @@ module Attributor
42
42
  end
43
43
 
44
44
 
45
- def parse(value, context=Attributor::DEFAULT_ROOT_CONTEXT)
46
- object = self.load(value,context)
45
+ def parse(value, context=Attributor::DEFAULT_ROOT_CONTEXT)
46
+ object = self.load(value,context)
47
47
 
48
- errors = self.validate(object,context)
49
- [ object, errors ]
50
- end
48
+ errors = self.validate(object,context)
49
+ [ object, errors ]
50
+ end
51
51
 
52
52
 
53
- def load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
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
- def describe(shallow=true, example: nil )
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|
@@ -25,8 +25,9 @@ module Attributor
25
25
 
26
26
  def [](k)
27
27
  unless @contents.key?(k)
28
- proc = lazy_attributes.delete k
29
- @contents[k] = proc.call
28
+ if (proc = lazy_attributes.delete k)
29
+ @contents[k] = proc.call
30
+ end
30
31
  end
31
32
  @contents[k]
32
33
  end
@@ -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.ancestors.find { |k| k.name && !k.name.empty? }.name
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 values [Array] Array of values to validate
157
- def self.validate(values, context=Attributor::DEFAULT_ROOT_CONTEXT, attribute=nil)
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 self.valid_type?(values)
160
- descriptive_type =if self.member_type != Object
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
- values.each_with_index.collect do |value, i|
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, **options)
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
- @contents.each_with_object({}) do |(k,v),hash|
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), result|
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
- result[name.to_sym] = attribute.dump(value, context: context + [name] )
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.path
20
+ value && value.read
17
21
  end
18
22
 
19
23
  def self.load(value,context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
@@ -1,3 +1,3 @@
1
1
  module Attributor
2
- VERSION = "3.0"
2
+ VERSION = "3.0.1"
3
3
  end
@@ -690,13 +690,12 @@ describe Attributor::Attribute do
690
690
  end
691
691
 
692
692
  it 'validates' do
693
- errors = attribute.validate(values)
694
- errors.should_not be_empty
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
 
@@ -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(collection_members).should =~ expected_errors
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(Attributor::IncompatibleTypeError, /cannot load values of type String/)
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: '3.0'
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-06-22 00:00:00.000000000 Z
12
+ date: 2015-07-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hashie