attributor 2.1.0 → 2.2.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/.gitignore +2 -0
- data/CHANGELOG.md +13 -2
- data/README.md +4 -1
- data/Rakefile +3 -11
- data/lib/attributor/attribute.rb +1 -1
- data/lib/attributor/dsl_compiler.rb +11 -0
- data/lib/attributor/type.rb +0 -7
- data/lib/attributor/types/collection.rb +3 -2
- data/lib/attributor/types/hash.rb +102 -53
- data/lib/attributor/types/model.rb +1 -1
- data/lib/attributor/version.rb +1 -1
- data/spec/support/types.rb +0 -2
- data/spec/types/collection_spec.rb +8 -0
- data/spec/types/hash_spec.rb +117 -8
- 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: 93e65f034891a43eb2164a43a0bd5bdf7d2a0e22
|
4
|
+
data.tar.gz: dc626f0b5d2cb575f490a1e1d8af6c60d232830f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 514f798b6bcb84313fa6c840dcb0a4bf9533cb3ec7f29a1c9a8ca00db45d119e7122a2bea02289fe094a6ab8db41ff13af0bfb47fafe9568186243f44d25b3f6
|
7
|
+
data.tar.gz: 848e54a8cdf56ab1a498ea75fbddc3571c7723a1285cc8b8c1085a2fa55e6b0a75494968c83f8154bab58f04531a684ea05114970e83c45b53a7c10e6e6a2883
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -4,7 +4,18 @@ Attributor Changelog
|
|
4
4
|
next
|
5
5
|
------
|
6
6
|
|
7
|
-
*
|
7
|
+
* next thing here
|
8
|
+
|
9
|
+
2.2.0
|
10
|
+
------
|
11
|
+
|
12
|
+
* Fix example generation for Hash and Collection to handle a non-Array context parameter.
|
13
|
+
* Hash:
|
14
|
+
* Added additional options:
|
15
|
+
* `:case_insensitive_load` for string-keyed hashes. This allows loading hashes with keys that do not exactly match the case defined in the hash.
|
16
|
+
* Added `:allow_extras` option to allow handling of undefined keys when loading.
|
17
|
+
* Added `Hash#set` to encapsulate the above options and attribute loading.
|
18
|
+
* Added `extra` command in the `keys` DSL, which lets you define a key (whose value should be a Hash), to group any unspecified keys during load.
|
8
19
|
|
9
20
|
2.1.0
|
10
21
|
------
|
@@ -23,7 +34,7 @@ next
|
|
23
34
|
* Enhanced error messages to report the correct context scope.
|
24
35
|
* Make Attribute assignments in models to report a special context (not the attributor root)
|
25
36
|
* Instead of reporting "$." as the context , when doing model.field_name=value, they'll now report "assignment.of(field_name)" instead
|
26
|
-
* Truncate the
|
37
|
+
* Truncate the length of values when reporting loading errors when they're long (i.e. >500 chars)
|
27
38
|
* `Model.attributes` may now be called more than once to set add or replace attributes. The exact behavior depends upon the types of the attributes being added or replaced. See [model_spec.rb](spec/types/model_spec.rb) for examples.
|
28
39
|
* Greately enhanced Hash type with individual key specification (rather than
|
29
40
|
simply defining the types of keys)
|
data/README.md
CHANGED
@@ -1,4 +1,7 @@
|
|
1
|
-
# Attributor
|
1
|
+
# Attributor [![TravisCI][travis-img-url]][travis-ci-url]
|
2
|
+
|
3
|
+
[travis-img-url]: https://travis-ci.org/rightscale/attributor.svg?branch=master
|
4
|
+
[travis-ci-url]:https://travis-ci.org/rightscale/attributor
|
2
5
|
|
3
6
|
An Attribute management, self documenting framework, designed for getting rid of most of your parameter handling boilerplate.
|
4
7
|
While initially designed to be the backbone for parameter handling in REST services, attribute management can be applied in many other areas.
|
data/Rakefile
CHANGED
@@ -1,20 +1,12 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
3
|
-
|
4
2
|
require 'bundler/setup'
|
5
|
-
#begin
|
6
|
-
# Bundler.setup(:default, :development)
|
7
|
-
#rescue Bundler::BundlerError => e
|
8
|
-
# $stderr.puts e.message
|
9
|
-
# $stderr.puts "Run `bundle install` to install missing gems"
|
10
|
-
# exit e.status_code
|
11
|
-
#end
|
12
3
|
require 'rake'
|
13
4
|
|
14
|
-
require 'rake/notes/rake_task'
|
15
|
-
|
16
5
|
require 'rspec/core'
|
17
6
|
require 'rspec/core/rake_task'
|
7
|
+
require 'bundler/gem_tasks'
|
8
|
+
require 'rake/notes/rake_task'
|
9
|
+
|
18
10
|
|
19
11
|
desc "Run RSpec code examples with simplecov"
|
20
12
|
RSpec::Core::RakeTask.new do |spec|
|
data/lib/attributor/attribute.rb
CHANGED
@@ -70,7 +70,7 @@ module Attributor
|
|
70
70
|
|
71
71
|
|
72
72
|
TOP_LEVEL_OPTIONS = [ :description, :values, :default, :example, :required, :required_if ]
|
73
|
-
INTERNAL_OPTIONS = [:dsl_compiler] # Options we don't want to expose when describing attributes
|
73
|
+
INTERNAL_OPTIONS = [:dsl_compiler,:dsl_compiler_options] # Options we don't want to expose when describing attributes
|
74
74
|
def describe(shallow=true)
|
75
75
|
description = { }
|
76
76
|
# Clone the common options
|
@@ -45,6 +45,17 @@ module Attributor
|
|
45
45
|
target.keys[name] = define(name, attr_type, **opts, &block)
|
46
46
|
end
|
47
47
|
|
48
|
+
def extra(name, attr_type=nil, **opts, &block)
|
49
|
+
if attr_type.nil?
|
50
|
+
attr_type = Attributor::Hash.of(key: target.key_type, value: target.value_type)
|
51
|
+
end
|
52
|
+
target.extra_keys = name
|
53
|
+
target.options[:allow_extra] = true
|
54
|
+
opts[:default] ||= {}
|
55
|
+
attr_type.options[:allow_extra] = true
|
56
|
+
key(name, attr_type, **opts, &block)
|
57
|
+
end
|
58
|
+
|
48
59
|
# Creates an Attributor:Attribute with given definition.
|
49
60
|
#
|
50
61
|
# @overload define(name, type, opts, &block)
|
data/lib/attributor/type.rb
CHANGED
@@ -43,7 +43,8 @@ module Attributor
|
|
43
43
|
result = []
|
44
44
|
size = rand(3) + 1
|
45
45
|
context ||= ["Collection-#{result.object_id}"]
|
46
|
-
|
46
|
+
context = Array(context)
|
47
|
+
|
47
48
|
size.times do |i|
|
48
49
|
subcontext = context + ["at(#{i})"]
|
49
50
|
result << self.member_attribute.example(subcontext)
|
@@ -84,7 +85,7 @@ module Attributor
|
|
84
85
|
#puts "Collection: #{self.type}"
|
85
86
|
hash = super(shallow)
|
86
87
|
hash[:options] = {} unless hash[:options]
|
87
|
-
hash[:
|
88
|
+
hash[:member_attribute] = self.member_attribute.describe
|
88
89
|
hash
|
89
90
|
end
|
90
91
|
|
@@ -9,27 +9,50 @@ module Attributor
|
|
9
9
|
attr_reader :key_type, :value_type, :options
|
10
10
|
attr_reader :value_attribute
|
11
11
|
attr_reader :key_attribute
|
12
|
+
attr_reader :insensitive_map
|
13
|
+
attr_accessor :extra_keys
|
12
14
|
end
|
13
15
|
|
14
16
|
@key_type = Object
|
15
17
|
@value_type = Object
|
16
|
-
|
17
18
|
|
18
19
|
@key_attribute = Attribute.new(@key_type)
|
19
20
|
@value_attribute = Attribute.new(@value_type)
|
20
21
|
|
22
|
+
def self.key_type=(key_type)
|
23
|
+
resolved_key_type = Attributor.resolve_type(key_type)
|
24
|
+
unless resolved_key_type.ancestors.include?(Attributor::Type)
|
25
|
+
raise Attributor::AttributorException.new("Hashes only support key types that are Attributor::Types. Got #{resolved_key_type.name}")
|
26
|
+
end
|
27
|
+
|
28
|
+
@key_type = resolved_key_type
|
29
|
+
@key_attribute = Attribute.new(@key_type)
|
30
|
+
@concrete=true
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.value_type=(value_type)
|
34
|
+
resolved_value_type = Attributor.resolve_type(value_type)
|
35
|
+
unless resolved_value_type.ancestors.include?(Attributor::Type)
|
36
|
+
raise Attributor::AttributorException.new("Hashes only support value types that are Attributor::Types. Got #{resolved_value_type.name}")
|
37
|
+
end
|
38
|
+
|
39
|
+
@value_type = resolved_value_type
|
40
|
+
@value_attribute = Attribute.new(@value_type)
|
41
|
+
@concrete=true
|
42
|
+
end
|
43
|
+
|
44
|
+
|
21
45
|
@saved_blocks = []
|
22
|
-
@options = {}
|
46
|
+
@options = {allow_extra: false}
|
23
47
|
@keys = {}
|
24
48
|
|
25
|
-
|
26
49
|
def self.inherited(klass)
|
27
50
|
k = self.key_type
|
28
51
|
v = self.value_type
|
29
52
|
|
30
53
|
klass.instance_eval do
|
31
54
|
@saved_blocks = []
|
32
|
-
@options = {}
|
55
|
+
@options = {allow_extra: false}
|
33
56
|
@keys = {}
|
34
57
|
@key_type = k
|
35
58
|
@value_type = v
|
@@ -38,6 +61,10 @@ module Attributor
|
|
38
61
|
end
|
39
62
|
end
|
40
63
|
|
64
|
+
def self.attributes(**options, &key_spec)
|
65
|
+
self.keys(options, &key_spec)
|
66
|
+
end
|
67
|
+
|
41
68
|
def self.keys(**options, &key_spec)
|
42
69
|
if block_given?
|
43
70
|
@saved_blocks << key_spec
|
@@ -57,6 +84,12 @@ module Attributor
|
|
57
84
|
blocks = @saved_blocks.shift(@saved_blocks.size)
|
58
85
|
compiler = dsl_class.new(self, opts)
|
59
86
|
compiler.parse(*blocks)
|
87
|
+
|
88
|
+
@insensitive_map = self.keys.keys.each_with_object({}) do |k, map|
|
89
|
+
map[k.downcase] = k
|
90
|
+
end
|
91
|
+
|
92
|
+
compiler
|
60
93
|
end
|
61
94
|
|
62
95
|
def self.dsl_class
|
@@ -73,42 +106,24 @@ module Attributor
|
|
73
106
|
|
74
107
|
# @example Hash.of(key: String, value: Integer)
|
75
108
|
def self.of(key: @key_type, value: @value_type)
|
76
|
-
if key
|
77
|
-
resolved_key_type = Attributor.resolve_type(key)
|
78
|
-
unless resolved_key_type.ancestors.include?(Attributor::Type)
|
79
|
-
raise Attributor::AttributorException.new("Hashes only support key types that are Attributor::Types. Got #{resolved_key_type.name}")
|
80
|
-
end
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
if value
|
85
|
-
resolved_value_type = Attributor.resolve_type(value)
|
86
|
-
unless resolved_value_type.ancestors.include?(Attributor::Type)
|
87
|
-
raise Attributor::AttributorException.new("Hashes only support value types that are Attributor::Types. Got #{resolved_value_type.name}")
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
109
|
Class.new(self) do
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
@key_attribute = Attribute.new(@key_type)
|
99
|
-
@value_attribute = Attribute.new(@value_type)
|
100
|
-
@concrete = true
|
110
|
+
self.key_type = key
|
111
|
+
self.value_type = value
|
101
112
|
@keys = {}
|
102
113
|
end
|
103
114
|
end
|
104
115
|
|
105
116
|
|
106
|
-
def self.construct(constructor_block,
|
117
|
+
def self.construct(constructor_block, **options)
|
107
118
|
return self if constructor_block.nil?
|
108
119
|
|
109
120
|
unless @concrete
|
110
121
|
return self.of(key:self.key_type, value: self.value_type)
|
111
|
-
|
122
|
+
.construct(constructor_block,**options)
|
123
|
+
end
|
124
|
+
|
125
|
+
if options[:case_insensitive_load] && !(self.key_type <= String)
|
126
|
+
raise Attributor::AttributorException.new(":case_insensitive_load may not be used with keys of type #{self.key_type.name}")
|
112
127
|
end
|
113
128
|
|
114
129
|
self.keys(options, &constructor_block)
|
@@ -120,7 +135,8 @@ module Attributor
|
|
120
135
|
return self.new if (key_type == Object && value_type == Object)
|
121
136
|
|
122
137
|
hash = ::Hash.new
|
123
|
-
|
138
|
+
context ||= ["#{Hash}-#{rand(10000000)}"]
|
139
|
+
context = Array(context)
|
124
140
|
|
125
141
|
if self.keys.any?
|
126
142
|
self.keys.each do |sub_name, sub_attribute|
|
@@ -128,12 +144,8 @@ module Attributor
|
|
128
144
|
hash[sub_name] = sub_attribute.example(subcontext)
|
129
145
|
end
|
130
146
|
else
|
131
|
-
|
132
147
|
size = rand(3) + 1
|
133
148
|
|
134
|
-
context ||= ["#{Hash}-#{rand(10000000)}"]
|
135
|
-
context = Array(context)
|
136
|
-
|
137
149
|
size.times do |i|
|
138
150
|
example_key = key_type.example(context + ["at(#{i})"])
|
139
151
|
subcontext = context + ["at(#{example_key})"]
|
@@ -158,12 +170,17 @@ module Attributor
|
|
158
170
|
|
159
171
|
def self.check_option!(name, definition)
|
160
172
|
case name
|
161
|
-
when :key_type
|
162
|
-
:ok
|
163
|
-
when :value_type
|
164
|
-
:ok
|
165
173
|
when :reference
|
166
174
|
:ok # FIXME ... actually do something smart
|
175
|
+
when :dsl_compiler
|
176
|
+
:ok
|
177
|
+
when :case_insensitive_load
|
178
|
+
unless self.key_type <= String
|
179
|
+
raise Attributor::AttributorException, ":case_insensitive_load may not be used with keys of type #{self.key_type.name}"
|
180
|
+
end
|
181
|
+
:ok
|
182
|
+
when :allow_extra
|
183
|
+
:ok
|
167
184
|
else
|
168
185
|
:unknown
|
169
186
|
end
|
@@ -195,23 +212,57 @@ module Attributor
|
|
195
212
|
end
|
196
213
|
|
197
214
|
def self.generate_subcontext(context, key_name)
|
198
|
-
context + ["
|
215
|
+
context + ["key(#{key_name.inspect})"]
|
216
|
+
end
|
217
|
+
|
218
|
+
def generate_subcontext(context, key_name)
|
219
|
+
self.class.generate_sub_context(context,key_name)
|
220
|
+
end
|
221
|
+
|
222
|
+
def set(key, value, context: self.generate_subcontext(Attributor::DEFAULT_ROOT_CONTEXT,key))
|
223
|
+
key = self.class.key_attribute.load(key, context)
|
224
|
+
|
225
|
+
if (attribute = self.class.keys[key])
|
226
|
+
return self[key] = attribute.load(value, context)
|
227
|
+
end
|
228
|
+
|
229
|
+
if self.class.options[:case_insensitive_load]
|
230
|
+
key = self.class.insensitive_map[key.downcase]
|
231
|
+
return self.set(key, value, context: context)
|
232
|
+
end
|
233
|
+
|
234
|
+
if self.class.options[:allow_extra]
|
235
|
+
if self.class.extra_keys.nil?
|
236
|
+
return self[key] = self.class.value_attribute.load(value, context)
|
237
|
+
else
|
238
|
+
return self[self.class.extra_keys].set(key, value, context: context)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
raise AttributorException, "Unknown key received: #{key.inspect} while loading #{Attributor.humanize_context(context)}"
|
199
243
|
end
|
200
244
|
|
201
245
|
def self.from_hash(object,context)
|
202
246
|
hash = self.new
|
203
247
|
|
204
|
-
|
205
|
-
|
248
|
+
# if the hash definition includes named extra keys, initialize
|
249
|
+
# its value from the object in case it provides some already.
|
250
|
+
# this is to ensure it exists when we handle any extra keys
|
251
|
+
# that may exist in the object later
|
252
|
+
if self.extra_keys
|
253
|
+
sub_context = self.generate_subcontext(context,self.extra_keys)
|
254
|
+
v = object.fetch(self.extra_keys, {})
|
255
|
+
hash.set(self.extra_keys, v, context: sub_context)
|
256
|
+
end
|
206
257
|
|
207
|
-
|
208
|
-
|
209
|
-
end
|
258
|
+
object.each do |k,v|
|
259
|
+
next if k == self.extra_keys
|
210
260
|
|
211
|
-
sub_context = self.generate_subcontext(
|
212
|
-
hash
|
261
|
+
sub_context = self.generate_subcontext(Attributor::DEFAULT_ROOT_CONTEXT,k)
|
262
|
+
hash.set(k, v, context: sub_context)
|
213
263
|
end
|
214
264
|
|
265
|
+
# handle default values for missing keys
|
215
266
|
self.keys.each do |key_name, attribute|
|
216
267
|
next if hash.key?(key_name)
|
217
268
|
sub_context = self.generate_subcontext(context,key_name)
|
@@ -262,8 +313,6 @@ module Attributor
|
|
262
313
|
|
263
314
|
def initialize(contents={})
|
264
315
|
@contents = contents
|
265
|
-
|
266
|
-
|
267
316
|
end
|
268
317
|
|
269
318
|
def key_type
|
@@ -293,7 +342,7 @@ module Attributor
|
|
293
342
|
|
294
343
|
if self.class.keys.any?
|
295
344
|
extra_keys = @contents.keys - self.class.keys.keys
|
296
|
-
if extra_keys.any?
|
345
|
+
if extra_keys.any? && !self.class.options[:allow_extra]
|
297
346
|
return extra_keys.collect do |k|
|
298
347
|
"#{Attributor.humanize_context(context)} can not have key: #{k.inspect}"
|
299
348
|
end
|
@@ -315,12 +364,12 @@ module Attributor
|
|
315
364
|
# FIXME: the sub contexts and error messages don't really make sense here
|
316
365
|
unless key_type == Attributor::Object
|
317
366
|
sub_context = context + ["key(#{key.inspect})"]
|
318
|
-
errors.push *key_attribute.validate(key, sub_context)
|
319
|
-
end
|
367
|
+
errors.push *key_attribute.validate(key, sub_context)
|
368
|
+
end
|
320
369
|
|
321
370
|
unless value_type == Attributor::Object
|
322
371
|
sub_context = context + ["value(#{value.inspect})"]
|
323
|
-
errors.push *value_attribute.validate(value, sub_context)
|
372
|
+
errors.push *value_attribute.validate(value, sub_context)
|
324
373
|
end
|
325
374
|
end
|
326
375
|
end
|
data/lib/attributor/version.rb
CHANGED
data/spec/support/types.rb
CHANGED
@@ -282,5 +282,13 @@ describe Attributor::Collection do
|
|
282
282
|
value.all? { |element| member_type.valid_type?(element) }.should be_true
|
283
283
|
end
|
284
284
|
end
|
285
|
+
context "passing a non array context" do
|
286
|
+
it 'still is handled correctly ' do
|
287
|
+
expect{
|
288
|
+
type.example("SimpleString")
|
289
|
+
}.to_not raise_error
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
285
293
|
end
|
286
294
|
end
|
data/spec/types/hash_spec.rb
CHANGED
@@ -6,6 +6,16 @@ describe Attributor::Hash do
|
|
6
6
|
subject(:type) { Attributor::Hash }
|
7
7
|
|
8
8
|
its(:native_type) { should be(type) }
|
9
|
+
its(:key_type) { should be(Attributor::Object) }
|
10
|
+
its(:value_type) { should be(Attributor::Object) }
|
11
|
+
|
12
|
+
context 'default options' do
|
13
|
+
subject(:options) { type.options }
|
14
|
+
it 'has allow_extra false' do
|
15
|
+
options[:allow_extra].should be(false)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
9
19
|
|
10
20
|
context '.example' do
|
11
21
|
context 'for a simple hash' do
|
@@ -25,6 +35,17 @@ describe Attributor::Hash do
|
|
25
35
|
example.values.all? {|v| v.kind_of? Integer}.should be(true)
|
26
36
|
end
|
27
37
|
end
|
38
|
+
context 'using a non array context' do
|
39
|
+
it 'should work for hashes with key/value types' do
|
40
|
+
expect{ Attributor::Hash.of(key:String,value:String).example("Not an Array") }.to_not raise_error
|
41
|
+
end
|
42
|
+
it 'should work for hashes with keys defined' do
|
43
|
+
block = proc { key 'a string', String }
|
44
|
+
hash = Attributor::Hash.of(key:String).construct(block)
|
45
|
+
|
46
|
+
expect{ hash.example("Not an Array") }.to_not raise_error
|
47
|
+
end
|
48
|
+
end
|
28
49
|
end
|
29
50
|
|
30
51
|
context '.load' do
|
@@ -124,7 +145,7 @@ describe Attributor::Hash do
|
|
124
145
|
subject(:type) { Attributor::Hash.construct(block) }
|
125
146
|
|
126
147
|
it { should_not be(Attributor::Hash)
|
127
|
-
|
148
|
+
}
|
128
149
|
|
129
150
|
context 'loading' do
|
130
151
|
let(:date) { DateTime.parse("2014-07-15") }
|
@@ -192,11 +213,18 @@ describe Attributor::Hash do
|
|
192
213
|
end
|
193
214
|
|
194
215
|
context '.check_option!' do
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
216
|
+
context ':case_insensitive_load' do
|
217
|
+
|
218
|
+
it 'is valid when key_type is a string' do
|
219
|
+
Attributor::Hash.of(key:String).check_option!(:case_insensitive_load, true).should == :ok
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'is invalid when key_type is non-string' do
|
223
|
+
expect {
|
224
|
+
Attributor::Hash.of(key:Integer).check_option!(:case_insensitive_load, true)
|
225
|
+
}.to raise_error(Attributor::AttributorException, /:case_insensitive_load may not be used/)
|
226
|
+
end
|
227
|
+
|
200
228
|
end
|
201
229
|
it 'rejects unknown options' do
|
202
230
|
subject.check_option!(:bad_option, Object).should == :unknown
|
@@ -271,7 +299,7 @@ describe Attributor::Hash do
|
|
271
299
|
end
|
272
300
|
end
|
273
301
|
|
274
|
-
let(:type) { Attributor::Hash.construct(block) }
|
302
|
+
let(:type) { Attributor::Hash.construct(block) }
|
275
303
|
|
276
304
|
let(:values) { {'integer' => 'one', 'datetime' => 'now' } }
|
277
305
|
subject(:hash) { type.new(values) }
|
@@ -279,7 +307,7 @@ describe Attributor::Hash do
|
|
279
307
|
it 'validates the keys' do
|
280
308
|
errors = hash.validate
|
281
309
|
errors.should have(3).items
|
282
|
-
errors.should include("Attribute $.
|
310
|
+
errors.should include("Attribute $.key(\"not-optional\") is required")
|
283
311
|
end
|
284
312
|
|
285
313
|
end
|
@@ -369,4 +397,85 @@ describe Attributor::Hash do
|
|
369
397
|
end
|
370
398
|
end
|
371
399
|
|
400
|
+
context 'with case_insensitive_load option for string keys' do
|
401
|
+
let(:block) do
|
402
|
+
proc do
|
403
|
+
key 'downcase', Integer
|
404
|
+
key 'UPCASE', Integer
|
405
|
+
key 'CamelCase', Integer
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
let(:type) { Attributor::Hash.of(key: String).construct(block, case_insensitive_load: true) }
|
410
|
+
|
411
|
+
let(:input) { {'DOWNCASE' => 1, 'upcase' => 2, 'CamelCase' => 3} }
|
412
|
+
|
413
|
+
subject(:output) { type.load(input) }
|
414
|
+
|
415
|
+
it 'maps the incoming keys to defined keys, regardless of case' do
|
416
|
+
output['downcase'].should eq(1)
|
417
|
+
output['UPCASE'].should eq(2)
|
418
|
+
output['CamelCase'].should eq(3)
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
|
423
|
+
context 'with allow_extra keys option' do
|
424
|
+
let(:type) do
|
425
|
+
Class.new(Attributor::Hash) do
|
426
|
+
self.value_type = String
|
427
|
+
|
428
|
+
keys allow_extra: true do
|
429
|
+
key :one, String
|
430
|
+
key :two, String, default: 'two'
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
let(:input) { {one: 'one', three: 3} }
|
436
|
+
subject(:output) { type.load(input) }
|
437
|
+
|
438
|
+
context 'that should be saved at the top level' do
|
439
|
+
its(:keys) { should =~ [:one, :two, :three] }
|
440
|
+
|
441
|
+
it 'loads the extra keys' do
|
442
|
+
output[:one].should eq('one')
|
443
|
+
output[:two].should eq('two')
|
444
|
+
output[:three].should eq('3')
|
445
|
+
end
|
446
|
+
|
447
|
+
its( :validate ){ should be_empty }
|
448
|
+
end
|
449
|
+
|
450
|
+
context 'that should be grouped into a sub-hash' do
|
451
|
+
before do
|
452
|
+
type.keys do
|
453
|
+
extra :options, Attributor::Hash.of(value: Integer)
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
its(:keys) { should =~ [:one, :two, :options] }
|
458
|
+
it 'loads the extra keys into :options sub-hash' do
|
459
|
+
output[:one].should eq('one')
|
460
|
+
output[:two].should eq('two')
|
461
|
+
output[:options].should eq({three: 3})
|
462
|
+
end
|
463
|
+
its( :validate ){ should be_empty }
|
464
|
+
|
465
|
+
context 'with an options value already provided' do
|
466
|
+
its(:keys) { should =~ [:one, :two, :options] }
|
467
|
+
let(:input) { {one: 'one', three: 3, options: {four: 4}} }
|
468
|
+
|
469
|
+
it 'loads the extra keys into :options sub-hash' do
|
470
|
+
output[:one].should eq('one')
|
471
|
+
output[:two].should eq('two')
|
472
|
+
output[:options].should eq({three: 3, four: 4})
|
473
|
+
end
|
474
|
+
its( :validate ){ should be_empty }
|
475
|
+
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
end
|
480
|
+
|
372
481
|
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: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -318,7 +318,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
318
318
|
version: '0'
|
319
319
|
requirements: []
|
320
320
|
rubyforge_project:
|
321
|
-
rubygems_version: 2.2.
|
321
|
+
rubygems_version: 2.2.2
|
322
322
|
signing_key:
|
323
323
|
specification_version: 4
|
324
324
|
summary: A powerful attribute and type management library for Ruby
|