representable 2.0.4 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +17 -0
- data/README.md +20 -1
- data/lib/representable.rb +2 -1
- data/lib/representable/binding.rb +115 -59
- data/lib/representable/config.rb +8 -0
- data/lib/representable/definition.rb +10 -14
- data/lib/representable/deserializer.rb +64 -25
- data/lib/representable/hash.rb +3 -3
- data/lib/representable/hash/binding.rb +40 -0
- data/lib/representable/hash/collection.rb +3 -2
- data/lib/representable/hash_methods.rb +4 -2
- data/lib/representable/mapper.rb +1 -1
- data/lib/representable/populator.rb +59 -0
- data/lib/representable/serializer.rb +24 -13
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +3 -3
- data/lib/representable/xml/binding.rb +171 -0
- data/lib/representable/yaml.rb +3 -3
- data/lib/representable/yaml/binding.rb +48 -0
- data/representable.gemspec +1 -1
- data/test/benchmarking.rb +83 -0
- data/test/binding_test.rb +46 -0
- data/test/definition_test.rb +5 -58
- data/test/exec_context_test.rb +4 -4
- data/test/hash_bindings_test.rb +4 -52
- data/test/hash_test.rb +6 -6
- data/test/json_test.rb +8 -8
- data/test/lonely_test.rb +1 -1
- data/test/realistic_benchmark.rb +83 -0
- data/test/skip_test.rb +28 -0
- data/test/xml_bindings_test.rb +2 -109
- data/test/xml_test.rb +61 -23
- data/test/yaml_test.rb +5 -8
- metadata +19 -11
- data/lib/representable/bindings/hash_bindings.rb +0 -64
- data/lib/representable/bindings/xml_bindings.rb +0 -172
- data/lib/representable/bindings/yaml_bindings.rb +0 -49
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 783116ac1174ef6a0aa6eaf44c768bfe3786dec0
|
4
|
+
data.tar.gz: bc85eaa1d218cd5c54161a607d80d6d3539937cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 347783301a5545df8e009130eb741970d5635796368c3aae726f52e21f0357daefbfa367e7296b053aa14c8357ac1dcdff95b2cca828bfe2bf3f79fc34defe86
|
7
|
+
data.tar.gz: ff5d1a1825af70bee387b09ee7f997718744e7a69b66f80fb379dbbd726e1640aec81888dd651cbfa6c78c218342bfc2a5305f1bf741972a677037e3d6176894
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
# 2.1.0
|
2
|
+
|
3
|
+
## Breaking Changes
|
4
|
+
|
5
|
+
* None, unless you messed around with internals like `Binding`.
|
6
|
+
|
7
|
+
## Changes
|
8
|
+
|
9
|
+
* Added `:skip_parse` to skip deserialization of fragments.
|
10
|
+
* It's now `Binding#read_fragment -> Populator -> Deserializer`. Mostly, this got changed to allow better support for complex collection semantics when populating/deserializing as found in Object-HAL.
|
11
|
+
* Likewise, it is `Binding#write_fragment -> Serializer`, clearly separating format-specific and generic logic.
|
12
|
+
* Make `Definition#inspect` more readable by filtering out some instance variables like `@runtime_options`.
|
13
|
+
* Remove `Binding#write_fragment_for`. This is `#render_fragment` now.
|
14
|
+
* Almost 100% speedup for rendering and parsing by removing Ruby's delegation and `method_missing`.
|
15
|
+
* Bindings are now in the following naming format: `Representable::Hash::Binding[::Collection]`. The file name is `representable/hash/binding`.
|
16
|
+
* Move `Definition#skipable_empty_value?` and `Definition#default_for` to `Binding` as it is runtime-specific.
|
17
|
+
|
1
18
|
# 2.0.4
|
2
19
|
|
3
20
|
* Fix implicit rendering of JSON and XML collections where json/collection wasn't loaded properly, resulting in the native JSON's `#to_json` to be called.
|
data/README.md
CHANGED
@@ -527,6 +527,7 @@ Here's a list of all dynamic options and their argument signature.
|
|
527
527
|
* `instance: lambda { |fragment, [i], args| }` ([see Object Creation](#polymorphic-object-creation))
|
528
528
|
* `reader: lambda { |document, args| }` ([see Read And Write](#overriding-read-and-write))
|
529
529
|
* `writer: lambda { |document, args| }` ([see Read And Write](#overriding-read-and-write))
|
530
|
+
* `skip_parse: lambda { |fragment, args| }` ([see Skip Parsing](#skip-parsing))
|
530
531
|
* `parse_filter: lambda { |fragment, document, args| }` ([see Filters](#filters)))
|
531
532
|
* `render_filter: lambda { |value, document, args| }` ([see Filters](#filters))
|
532
533
|
* `if: lambda { |args| }` ([see Conditions](#conditions))
|
@@ -571,6 +572,24 @@ property :title, parse_filter: lambda { |fragment, doc, *args| Sanitizer.call(fr
|
|
571
572
|
Just before setting the fragment to the object via the `:setter`, the `:parse_filter` is called.
|
572
573
|
|
573
574
|
|
575
|
+
## Skip Parsing
|
576
|
+
|
577
|
+
You can skip parsing for particular fragments which will completely ignore them as if they weren't present in the parsed document.
|
578
|
+
|
579
|
+
```ruby
|
580
|
+
property :title, skip_parse: lambda { |fragment, options| fragment.blank? }
|
581
|
+
```
|
582
|
+
|
583
|
+
Note that when used with collections, this is evaluated per item.
|
584
|
+
|
585
|
+
```ruby
|
586
|
+
collection :songs, skip_parse: lambda { |fragment, options| fragment["title"].blank? } do
|
587
|
+
property :title
|
588
|
+
end
|
589
|
+
```
|
590
|
+
|
591
|
+
This won't parse empty incoming songs in the collection.
|
592
|
+
|
574
593
|
## Callable Options
|
575
594
|
|
576
595
|
While lambdas are one option for dynamic options, you might also pass a "callable" object to a directive.
|
@@ -838,7 +857,7 @@ module AlbumRepresenter
|
|
838
857
|
end
|
839
858
|
```
|
840
859
|
|
841
|
-
The block for `:class` receives the currently parsed fragment. Here, this might be
|
860
|
+
The block for `:class` receives the currently parsed fragment. Here, this might be something like `{"title"=>"Weirdo", "track"=>5}`.
|
842
861
|
|
843
862
|
If this is not enough, you may override the entire object creation process using `:instance`.
|
844
863
|
|
data/lib/representable.rb
CHANGED
@@ -41,7 +41,9 @@ private
|
|
41
41
|
|
42
42
|
def representable_bindings_for(format, options)
|
43
43
|
options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
|
44
|
+
|
44
45
|
representable_attrs.collect {|attr| representable_binding_for(attr, format, options) }
|
46
|
+
# representable_attrs.binding_cache[format] ||= representable_attrs.collect {|attr| representable_binding_for(attr, format, options) }
|
45
47
|
end
|
46
48
|
|
47
49
|
def representable_binding_for(attribute, format, options)
|
@@ -61,7 +63,6 @@ private
|
|
61
63
|
Mapper.new(bindings, represented, options) # TODO: remove self, or do we need it? and also represented!
|
62
64
|
end
|
63
65
|
|
64
|
-
|
65
66
|
def representation_wrap(*args)
|
66
67
|
representable_attrs.wrap_for(self.class.name, represented, *args)
|
67
68
|
end
|
@@ -1,8 +1,14 @@
|
|
1
|
+
require "representable/populator"
|
1
2
|
require "representable/deserializer"
|
2
3
|
require "representable/serializer"
|
3
4
|
|
4
5
|
module Representable
|
5
6
|
# The Binding wraps the Definition instance for this property and provides methods to read/write fragments.
|
7
|
+
|
8
|
+
# The flow when parsing is Binding#read_fragment -> Populator -> Deserializer.
|
9
|
+
# Actual parsing the fragment from the document happens in Binding#read, everything after that is generic.
|
10
|
+
#
|
11
|
+
# Serialization: Serializer -> {frag}/[frag]/frag -> Binding#write
|
6
12
|
class Binding
|
7
13
|
class FragmentNotFound
|
8
14
|
end
|
@@ -15,11 +21,8 @@ module Representable
|
|
15
21
|
|
16
22
|
def initialize(definition, represented, decorator, user_options={}) # TODO: remove default arg for user options.
|
17
23
|
@definition = definition
|
18
|
-
@represented = represented
|
19
|
-
@decorator = decorator
|
20
|
-
@user_options = user_options
|
21
24
|
|
22
|
-
|
25
|
+
setup!(represented, decorator, user_options) # this can be used in #compile_fragment/#uncompile_fragment in case we wanna reuse the Binding instance.
|
23
26
|
end
|
24
27
|
|
25
28
|
attr_reader :user_options, :represented # TODO: make private/remove.
|
@@ -39,37 +42,28 @@ module Representable
|
|
39
42
|
# Parse value from doc and update the model property.
|
40
43
|
def uncompile_fragment(doc)
|
41
44
|
evaluate_option(:reader, doc) do
|
42
|
-
read_fragment(doc)
|
43
|
-
value = parse_filter(value, doc)
|
44
|
-
set(value)
|
45
|
-
end
|
45
|
+
read_fragment(doc)
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
49
|
def write_fragment(doc, value)
|
50
50
|
value = default_for(value)
|
51
51
|
|
52
|
-
write_fragment_for(value, doc)
|
53
|
-
end
|
54
|
-
|
55
|
-
def write_fragment_for(value, doc)
|
56
52
|
return if skipable_empty_value?(value)
|
57
|
-
write(doc, value)
|
58
|
-
end
|
59
53
|
|
60
|
-
|
61
|
-
|
54
|
+
render_fragment(value, doc)
|
55
|
+
end
|
62
56
|
|
63
|
-
|
64
|
-
|
65
|
-
value = self[:default]
|
66
|
-
end
|
57
|
+
def render_fragment(value, doc)
|
58
|
+
fragment = serialize(value) # render fragments of hash, xml, yaml.
|
67
59
|
|
68
|
-
|
60
|
+
write(doc, fragment)
|
69
61
|
end
|
70
62
|
|
71
|
-
def
|
72
|
-
read(doc)
|
63
|
+
def read_fragment(doc)
|
64
|
+
fragment = read(doc) # scalar, Array, or Hash (abstract format) or un-deserialised fragment(s).
|
65
|
+
|
66
|
+
populator.call(fragment, doc)
|
73
67
|
end
|
74
68
|
|
75
69
|
def render_filter(value, doc)
|
@@ -98,11 +92,75 @@ module Representable
|
|
98
92
|
evaluate_option(:extend, object) # TODO: pass args? do we actually have args at the time this is called (compile-time)?
|
99
93
|
end
|
100
94
|
|
95
|
+
# Evaluate the option (either nil, static, a block or an instance method call) or
|
96
|
+
# executes passed block when option not defined.
|
97
|
+
def evaluate_option(name, *args)
|
98
|
+
unless proc = self[name]
|
99
|
+
return yield if block_given?
|
100
|
+
return
|
101
|
+
end
|
102
|
+
|
103
|
+
# TODO: it would be better if user_options was nil per default and then we just don't pass it into lambdas.
|
104
|
+
options = self[:pass_options] ? Options.new(self, user_options, represented, decorator) : user_options
|
105
|
+
|
106
|
+
proc.evaluate(exec_context, *(args<<options)) # from Uber::Options::Value.
|
107
|
+
end
|
108
|
+
|
109
|
+
def [](name)
|
110
|
+
@definition[name]
|
111
|
+
end
|
112
|
+
# TODO: i don't want to define all methods here, but it is faster!
|
113
|
+
# TODO: test public interface.
|
114
|
+
def getter
|
115
|
+
@definition.getter
|
116
|
+
end
|
117
|
+
def setter
|
118
|
+
@definition.setter
|
119
|
+
end
|
120
|
+
def typed?
|
121
|
+
@definition.typed?
|
122
|
+
end
|
123
|
+
def representable?
|
124
|
+
@definition.representable?
|
125
|
+
end
|
126
|
+
def has_default?(*args)
|
127
|
+
@definition.has_default?(*args)
|
128
|
+
end
|
129
|
+
def name
|
130
|
+
@definition.name
|
131
|
+
end
|
132
|
+
def representer_module
|
133
|
+
@definition.representer_module
|
134
|
+
end
|
135
|
+
# perf : 1.7-1.9
|
136
|
+
#extend Forwardable
|
137
|
+
#def_delegators :@definition, *%w([] getter setter typed? representable? has_default? name representer_module)
|
138
|
+
# perf : 1.7-1.9
|
139
|
+
# %w([] getter setter typed? representable? has_default? name representer_module).each do |name|
|
140
|
+
# define_method(name.to_sym) { |*args| @definition.send(name, *args) }
|
141
|
+
# end
|
142
|
+
|
143
|
+
def skipable_empty_value?(value)
|
144
|
+
return true if array? and self[:render_empty] == false and value and value.size == 0 # TODO: change in 2.0, don't render emtpy.
|
145
|
+
value.nil? and not self[:render_nil]
|
146
|
+
end
|
147
|
+
|
148
|
+
def default_for(value)
|
149
|
+
return self[:default] if skipable_empty_value?(value)
|
150
|
+
value
|
151
|
+
end
|
152
|
+
|
153
|
+
def array?
|
154
|
+
@definition.array?
|
155
|
+
end
|
156
|
+
|
101
157
|
private
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
@
|
158
|
+
def setup!(represented, decorator, user_options)
|
159
|
+
@represented = represented
|
160
|
+
@decorator = decorator
|
161
|
+
@user_options = user_options
|
162
|
+
|
163
|
+
setup_exec_context!
|
106
164
|
end
|
107
165
|
|
108
166
|
def setup_exec_context!
|
@@ -115,18 +173,24 @@ module Representable
|
|
115
173
|
|
116
174
|
attr_reader :exec_context, :decorator
|
117
175
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
unless proc = self[name]
|
122
|
-
return yield if block_given?
|
123
|
-
return
|
124
|
-
end
|
176
|
+
def serialize(object)
|
177
|
+
serializer.call(object)
|
178
|
+
end
|
125
179
|
|
126
|
-
|
127
|
-
|
180
|
+
def serializer_class
|
181
|
+
Serializer
|
182
|
+
end
|
128
183
|
|
129
|
-
|
184
|
+
def serializer
|
185
|
+
serializer_class.new(self)
|
186
|
+
end
|
187
|
+
|
188
|
+
def populator
|
189
|
+
populator_class.new(self)
|
190
|
+
end
|
191
|
+
|
192
|
+
def populator_class
|
193
|
+
Populator
|
130
194
|
end
|
131
195
|
|
132
196
|
|
@@ -135,35 +199,27 @@ module Representable
|
|
135
199
|
Options = Struct.new(:binding, :user_options, :represented, :decorator)
|
136
200
|
|
137
201
|
|
138
|
-
#
|
139
|
-
module
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
def deserialize(data)
|
145
|
-
# DISCUSS: does it make sense to skip deserialization of nil-values here?
|
146
|
-
ObjectDeserializer.new(self).call(data)
|
202
|
+
# generics for collection bindings.
|
203
|
+
module Collection
|
204
|
+
private
|
205
|
+
def populator_class
|
206
|
+
Populator::Collection
|
147
207
|
end
|
148
208
|
|
149
|
-
def
|
150
|
-
|
209
|
+
def serializer_class
|
210
|
+
Serializer::Collection
|
151
211
|
end
|
212
|
+
end
|
152
213
|
|
214
|
+
# and the same for hashes.
|
215
|
+
module Hash
|
153
216
|
private
|
154
|
-
|
155
|
-
|
156
|
-
item_class = class_from(fragment, *args) or raise DeserializeError.new(":class did not return class constant.")
|
157
|
-
item_class.new
|
158
|
-
end
|
159
|
-
|
160
|
-
def class_from(fragment, *args)
|
161
|
-
evaluate_option(:class, fragment, *args)
|
217
|
+
def populator_class
|
218
|
+
Populator::Hash
|
162
219
|
end
|
163
220
|
|
164
|
-
def
|
165
|
-
|
166
|
-
evaluate_option(:instance, fragment, *args) { return } or raise DeserializeError.new(":instance did not return object.")
|
221
|
+
def serializer_class
|
222
|
+
Serializer::Hash
|
167
223
|
end
|
168
224
|
end
|
169
225
|
end
|
data/lib/representable/config.rb
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
# Caching of Bindings
|
2
|
+
# in Decorators, this could be an instance class var. when inherited, it is automatically busted.
|
3
|
+
# however, in modules we can't do that. we never know when a module is "finalised", so we don't really know when to bust the cache.
|
4
|
+
|
1
5
|
module Representable
|
2
6
|
# Config contains three independent, inheritable directives: features, options and definitions.
|
3
7
|
# It is a hash - just add directives if you need them.
|
@@ -77,6 +81,10 @@ module Representable
|
|
77
81
|
value
|
78
82
|
end
|
79
83
|
|
84
|
+
# def binding_cache
|
85
|
+
# @binding_cache ||= {}
|
86
|
+
# end
|
87
|
+
|
80
88
|
private
|
81
89
|
def infer_name_for(name)
|
82
90
|
name.to_s.split('::').last.
|
@@ -33,8 +33,9 @@ module Representable
|
|
33
33
|
self
|
34
34
|
end
|
35
35
|
|
36
|
-
|
37
|
-
|
36
|
+
def [](name)
|
37
|
+
@runtime_options[name]
|
38
|
+
end
|
38
39
|
|
39
40
|
def clone
|
40
41
|
self.class.new(name, @options.clone)
|
@@ -49,7 +50,7 @@ module Representable
|
|
49
50
|
end
|
50
51
|
|
51
52
|
def representable?
|
52
|
-
return if self[:representable]
|
53
|
+
return if self[:representable] == false
|
53
54
|
self[:representable] or typed?
|
54
55
|
end
|
55
56
|
|
@@ -61,11 +62,6 @@ module Representable
|
|
61
62
|
self[:hash]
|
62
63
|
end
|
63
64
|
|
64
|
-
def default_for(value)
|
65
|
-
return self[:default] if skipable_empty_value?(value)
|
66
|
-
value
|
67
|
-
end
|
68
|
-
|
69
65
|
def has_default?
|
70
66
|
@options.has_key?(:default)
|
71
67
|
end
|
@@ -74,15 +70,15 @@ module Representable
|
|
74
70
|
@options[:extend]
|
75
71
|
end
|
76
72
|
|
77
|
-
def skipable_empty_value?(value)
|
78
|
-
return true if array? and self[:render_empty] == false and value and value.size == 0 # TODO: change in 2.0, don't render emtpy.
|
79
|
-
value.nil? and not self[:render_nil]
|
80
|
-
end
|
81
|
-
|
82
73
|
def create_binding(*args)
|
83
74
|
self[:binding].call(self, *args)
|
84
75
|
end
|
85
76
|
|
77
|
+
def inspect
|
78
|
+
state = (instance_variables-[:@runtime_options, :@name]).collect { |ivar| "#{ivar}=#{instance_variable_get(ivar)}" }
|
79
|
+
"#<Representable::Definition ==>#{name} #{state.join(" ")}>"
|
80
|
+
end
|
81
|
+
|
86
82
|
private
|
87
83
|
def setup!(options, &block)
|
88
84
|
handle_extend!(options)
|
@@ -109,7 +105,7 @@ module Representable
|
|
109
105
|
end
|
110
106
|
|
111
107
|
def dynamic_options
|
112
|
-
[:as, :getter, :setter, :class, :instance, :reader, :writer, :extend, :prepare, :if, :deserialize, :serialize, :render_filter, :parse_filter]
|
108
|
+
[:as, :getter, :setter, :class, :instance, :reader, :writer, :extend, :prepare, :if, :deserialize, :serialize, :render_filter, :parse_filter, :skip_parse]
|
113
109
|
end
|
114
110
|
|
115
111
|
def handle_extend!(options)
|
@@ -1,24 +1,14 @@
|
|
1
1
|
module Representable
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@deserializer = ObjectDeserializer.new(@binding)
|
13
|
-
|
14
|
-
@deserializer.call(item_fragment, i) # FIXME: what if obj nil?
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
|
20
|
-
class ObjectDeserializer
|
21
|
-
# dependencies: Def#options, Def#create_object
|
2
|
+
# Deserializer's job is deserializing the already parsed fragment into a scalar or an object.
|
3
|
+
# This object is then returned to the Populator.
|
4
|
+
#
|
5
|
+
# It respects :deserialize, :prepare, :class, :instance
|
6
|
+
#
|
7
|
+
# Collection bindings return an array of parsed fragment items (still native format, e.g. Nokogiri node, for nested objects).
|
8
|
+
#
|
9
|
+
# Workflow
|
10
|
+
# call -> instance/class -> prepare -> deserialize -> from_json.
|
11
|
+
class Deserializer
|
22
12
|
def initialize(binding)
|
23
13
|
@binding = binding
|
24
14
|
end
|
@@ -27,9 +17,7 @@ module Representable
|
|
27
17
|
return fragment unless @binding.typed? # customize with :extend. this is not really straight-forward.
|
28
18
|
|
29
19
|
# what if create_object is responsible for providing the deserialize-to object?
|
30
|
-
object
|
31
|
-
|
32
|
-
# DISCUSS: what parts should be in this class, what in Binding?
|
20
|
+
object = create_object(fragment, *args) # customize with :instance and :class.
|
33
21
|
representable = prepare(object) # customize with :prepare and :extend.
|
34
22
|
|
35
23
|
deserialize(representable, fragment, @binding.user_options) # deactivate-able via :representable => false.
|
@@ -39,13 +27,13 @@ module Representable
|
|
39
27
|
def deserialize(object, fragment, options) # TODO: merge with #serialize.
|
40
28
|
return object unless @binding.representable?
|
41
29
|
|
42
|
-
@binding.
|
30
|
+
@binding.evaluate_option(:deserialize, object, fragment) do
|
43
31
|
object.send(@binding.deserialize_method, fragment, options)
|
44
32
|
end
|
45
33
|
end
|
46
34
|
|
47
35
|
def prepare(object)
|
48
|
-
@binding.
|
36
|
+
@binding.evaluate_option(:prepare, object) do
|
49
37
|
prepare!(object)
|
50
38
|
end
|
51
39
|
end
|
@@ -59,5 +47,56 @@ module Representable
|
|
59
47
|
mod.prepare(object)
|
60
48
|
end
|
61
49
|
# in deserialize, we should get the original object?
|
50
|
+
|
51
|
+
def create_object(fragment, *args)
|
52
|
+
instance_for(fragment, *args) or class_for(fragment, *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
def class_for(fragment, *args)
|
56
|
+
item_class = class_from(fragment, *args) or raise DeserializeError.new(":class did not return class constant.")
|
57
|
+
item_class.new
|
58
|
+
end
|
59
|
+
|
60
|
+
def class_from(fragment, *args)
|
61
|
+
@binding.evaluate_option(:class, fragment, *args)
|
62
|
+
end
|
63
|
+
|
64
|
+
def instance_for(fragment, *args)
|
65
|
+
# cool: if no :instance set, { return } will jump out of this method.
|
66
|
+
@binding.evaluate_option(:instance, fragment, *args) { return } or raise DeserializeError.new(":instance did not return object.")
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
# Collection does exactly the same as Deserializer but for a collection.
|
72
|
+
class Collection < self
|
73
|
+
def call(fragment)
|
74
|
+
collection = [] # this can be replaced, e.g. AR::Collection or whatever.
|
75
|
+
|
76
|
+
fragment.each_with_index do |item_fragment, i|
|
77
|
+
# add more per-item options here!
|
78
|
+
next if @binding.evaluate_option(:skip_parse, item_fragment)
|
79
|
+
|
80
|
+
collection << deserialize!(item_fragment, i) # FIXME: what if obj nil?
|
81
|
+
end
|
82
|
+
|
83
|
+
collection # with parse_strategy: :sync, this is ignored.
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def deserialize!(*args)
|
88
|
+
# TODO: re-use deserializer.
|
89
|
+
Deserializer.new(@binding).call(*args)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
class Hash < Collection
|
95
|
+
def call(hash)
|
96
|
+
{}.tap do |hsh|
|
97
|
+
hash.each { |key, fragment| hsh[key] = deserialize!(fragment) }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
62
101
|
end
|
63
102
|
end
|