representable 2.0.4 → 2.1.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/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
|