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 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