representable 2.4.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +7 -12
- data/CHANGES.md +6 -27
- data/README.md +28 -1326
- data/lib/representable.rb +4 -14
- data/lib/representable/binding.rb +3 -7
- data/lib/representable/definition.rb +1 -2
- data/lib/representable/populator.rb +35 -0
- data/lib/representable/version.rb +1 -1
- data/test/definition_test.rb +0 -7
- data/test/exec_context_test.rb +2 -2
- data/test/instance_test.rb +0 -19
- data/test/realistic_benchmark.rb +0 -13
- data/test/representable_test.rb +0 -16
- data/test/skip_test.rb +1 -1
- data/test/test_helper.rb +0 -2
- metadata +3 -65
- data/lib/representable/deprecations.rb +0 -127
- data/lib/representable/parse_strategies.rb +0 -93
- data/test-with-deprecations/as_test.rb +0 -65
- data/test-with-deprecations/benchmarking.rb +0 -83
- data/test-with-deprecations/binding_test.rb +0 -46
- data/test-with-deprecations/blaaaaaaaa_test.rb +0 -69
- data/test-with-deprecations/cached_test.rb +0 -147
- data/test-with-deprecations/class_test.rb +0 -119
- data/test-with-deprecations/coercion_test.rb +0 -52
- data/test-with-deprecations/config/inherit_test.rb +0 -135
- data/test-with-deprecations/config_test.rb +0 -122
- data/test-with-deprecations/decorator_scope_test.rb +0 -28
- data/test-with-deprecations/decorator_test.rb +0 -96
- data/test-with-deprecations/default_test.rb +0 -34
- data/test-with-deprecations/defaults_options_test.rb +0 -93
- data/test-with-deprecations/definition_test.rb +0 -264
- data/test-with-deprecations/example.rb +0 -310
- data/test-with-deprecations/examples/object.rb +0 -31
- data/test-with-deprecations/exec_context_test.rb +0 -93
- data/test-with-deprecations/features_test.rb +0 -70
- data/test-with-deprecations/filter_test.rb +0 -57
- data/test-with-deprecations/for_collection_test.rb +0 -74
- data/test-with-deprecations/generic_test.rb +0 -116
- data/test-with-deprecations/getter_setter_test.rb +0 -21
- data/test-with-deprecations/hash_bindings_test.rb +0 -87
- data/test-with-deprecations/hash_test.rb +0 -160
- data/test-with-deprecations/heritage_test.rb +0 -62
- data/test-with-deprecations/if_test.rb +0 -79
- data/test-with-deprecations/include_exclude_test.rb +0 -88
- data/test-with-deprecations/inherit_test.rb +0 -159
- data/test-with-deprecations/inline_test.rb +0 -272
- data/test-with-deprecations/instance_test.rb +0 -266
- data/test-with-deprecations/is_representable_test.rb +0 -77
- data/test-with-deprecations/json_test.rb +0 -355
- data/test-with-deprecations/lonely_test.rb +0 -239
- data/test-with-deprecations/mongoid_test.rb +0 -31
- data/test-with-deprecations/nested_test.rb +0 -115
- data/test-with-deprecations/object_test.rb +0 -60
- data/test-with-deprecations/parse_pipeline_test.rb +0 -64
- data/test-with-deprecations/parse_strategy_test.rb +0 -279
- data/test-with-deprecations/pass_options_test.rb +0 -27
- data/test-with-deprecations/pipeline_test.rb +0 -277
- data/test-with-deprecations/populator_test.rb +0 -105
- data/test-with-deprecations/prepare_test.rb +0 -67
- data/test-with-deprecations/private_options_test.rb +0 -18
- data/test-with-deprecations/reader_writer_test.rb +0 -19
- data/test-with-deprecations/realistic_benchmark.rb +0 -115
- data/test-with-deprecations/render_nil_test.rb +0 -21
- data/test-with-deprecations/represent_test.rb +0 -88
- data/test-with-deprecations/representable_test.rb +0 -511
- data/test-with-deprecations/schema_test.rb +0 -148
- data/test-with-deprecations/serialize_deserialize_test.rb +0 -33
- data/test-with-deprecations/skip_test.rb +0 -81
- data/test-with-deprecations/stringify_hash_test.rb +0 -41
- data/test-with-deprecations/test_helper.rb +0 -135
- data/test-with-deprecations/test_helper_test.rb +0 -25
- data/test-with-deprecations/uncategorized_test.rb +0 -67
- data/test-with-deprecations/user_options_test.rb +0 -15
- data/test-with-deprecations/wrap_test.rb +0 -152
- data/test-with-deprecations/xml_bindings_test.rb +0 -62
- data/test-with-deprecations/xml_test.rb +0 -503
- data/test-with-deprecations/yaml_test.rb +0 -162
- data/test/parse_strategy_test.rb +0 -279
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5caccf4a60cbe2ca6066a047755a13a741b488b1
|
4
|
+
data.tar.gz: aa57e2ec29209f936b58642ac84cfdf223e6d09b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9c5047e0c91d61a43677bd92cc64fbee0e34292b3b24ea59d10e2962075626c8c6dddd4b64c21092f65ed4d3abcfa1458f318bfd4d179085052c53565eb1d8ec
|
7
|
+
data.tar.gz: be1d272ce967973dbac844beb02f7d1418adb9f1c59b02ca9472a912bde994ffa7543158c3f8543ae5a172dda49c1f838ce963e5413e5c83b204bddd8aa9366c
|
data/.travis.yml
CHANGED
@@ -1,12 +1,7 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
- rvm: 2.2.0
|
9
|
-
# FIXME: add rbx and jruby, again, fix ruby-prof.
|
10
|
-
# - rvm: rbx-2.2.10
|
11
|
-
# - rvm: jruby-19mode
|
12
|
-
|
1
|
+
rvm:
|
2
|
+
- 1.9.3
|
3
|
+
- 2.0.0
|
4
|
+
- 2.1.1
|
5
|
+
- 2.2.3
|
6
|
+
before_install:
|
7
|
+
- gem install bundler
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 3.0.0
|
2
|
+
|
3
|
+
* Removed deprecations from 2.4.
|
4
|
+
* Removed `:parse_strategy` in favor of `:populator`.
|
5
|
+
* Removed `:binding` in favor of your own pipeline.
|
6
|
+
|
1
7
|
# 2.4.1
|
2
8
|
|
3
9
|
* No need to use Uber::Callable in Pipeline as this object is always invoked via `#call`.
|
@@ -13,33 +19,6 @@ render_filter: val, doc, options
|
|
13
19
|
representer_class.representable_attrs is definitions
|
14
20
|
* Removed `:use_decorator` option. Use a decorator instead.
|
15
21
|
|
16
|
-
TODO: DEPRECATE 4-args? also, allow options[:fragment] instead of options[:result] ?
|
17
|
-
|
18
|
-
|
19
|
-
TODO: :representable should be removing Deserialize.
|
20
|
-
deprecate Coercion, it sucks
|
21
|
-
deprecate parse/render filter
|
22
|
-
|
23
|
-
pipeline
|
24
|
-
less ifs, instead: simply remove the "middleware"
|
25
|
-
read_fragment
|
26
|
-
represented.set instance.prepare.from_json
|
27
|
-
Populator.(fragment)
|
28
|
-
not_found?.default.deserialize.parse_filter.set
|
29
|
-
Deserializer.(fragment)
|
30
|
-
instance.class.prepare.deserialize=from_json
|
31
|
-
|
32
|
-
OverwriteOnNil vs. StopOnNil
|
33
|
-
[Instance, ->(){..}, Bla]
|
34
|
-
|
35
|
-
did you ever overwrite #create_object in a binding? you can now simply pass a callable object into the pipeline.
|
36
|
-
TODO: deprecate parse strategies in favour of :populator (analogue to Reform)
|
37
|
-
|
38
|
-
elegant, easier to understand, and extend
|
39
|
-
you know, the whole deserialize stack (same for rendering): you pass in the json, it finds the fragments for each property, passes it to the binding, the binding deserializes, builds/finds a model, assigns values to it, and so on
|
40
|
-
you can partially hook in with :instance or :deserialize or :parse_filter, but it's really awkward. i am now changing the architecture to a pipeline, where you plug in the features you want (e.g. "do not call :prepare, :instance, :class, etc. but run my own :populator")
|
41
|
-
and it suddenly is super simple to understand
|
42
|
-
|
43
22
|
* Added `Representable.deprecations = false` to disable slow and weird deprecation code.
|
44
23
|
|
45
24
|
* Removed `Binding#user_options`. This is now available via `->(options[:user_options])`.
|
data/README.md
CHANGED
@@ -12,30 +12,9 @@ In other words: Take an object and decorate it with a representer module. This w
|
|
12
12
|
Representable is helpful for all kind of mappings, rendering and parsing workflows. However, it is mostly useful in API code. Are you planning to write a real REST API with representable? Then check out the [Roar](http://github.com/apotonick/roar) gem first, save work and time and make the world a better place instead.
|
13
13
|
|
14
14
|
|
15
|
-
##
|
16
|
-
|
17
|
-
The representable gem runs with all Ruby versions >= 1.9.3.
|
18
|
-
|
19
|
-
```ruby
|
20
|
-
gem 'representable'
|
21
|
-
```
|
22
|
-
|
23
|
-
### Dependencies
|
24
|
-
|
25
|
-
Representable does a great job with JSON, it also features support for XML, YAML and pure ruby
|
26
|
-
hashes. But Representable did not bundle dependencies for JSON and XML.
|
27
|
-
|
28
|
-
If you want to use JSON, add the following to your Gemfile:
|
29
|
-
|
30
|
-
```ruby
|
31
|
-
gem 'multi_json'
|
32
|
-
```
|
33
|
-
|
34
|
-
If you want to use XML, add the following to your Gemfile:
|
15
|
+
## Full Documentation
|
35
16
|
|
36
|
-
|
37
|
-
gem 'nokogiri'
|
38
|
-
```
|
17
|
+
Representable comes with a rich set of options and semantics for parsing and rendering documents. Its [full documentation](http://trailblazer.to/gems/representable/3.0/api.html) can be found on the Trailblazer site.
|
39
18
|
|
40
19
|
## Example
|
41
20
|
|
@@ -50,12 +29,14 @@ song = Song.new(title: "Fallout", track: 1)
|
|
50
29
|
|
51
30
|
## Defining Representations
|
52
31
|
|
53
|
-
Representations are defined using representer modules.
|
32
|
+
Representations are defined using representer classes, called _decorator, or modules.
|
33
|
+
|
34
|
+
In these examples, let's use decorators
|
54
35
|
|
55
36
|
```ruby
|
56
37
|
require 'representable/json'
|
57
38
|
|
58
|
-
|
39
|
+
class SongRepresenter < Representable::Decorator
|
59
40
|
include Representable::JSON
|
60
41
|
|
61
42
|
property :title
|
@@ -71,7 +52,7 @@ In the representer the #property method allows declaring represented attributes
|
|
71
52
|
Mixing in the representer into the object adds a rendering method.
|
72
53
|
|
73
54
|
```ruby
|
74
|
-
|
55
|
+
SongRepresenter.new(song).to_json
|
75
56
|
#=> {"title":"Fallout","track":1}
|
76
57
|
```
|
77
58
|
|
@@ -80,56 +61,11 @@ song.extend(SongRepresenter).to_json
|
|
80
61
|
It also adds support for parsing.
|
81
62
|
|
82
63
|
```ruby
|
83
|
-
song =
|
64
|
+
song = SongRepresenter.new(song).from_json(%{ {"title":"Roxanne"} })
|
84
65
|
#=> #<Song title="Roxanne", track=nil>
|
85
66
|
```
|
86
67
|
|
87
|
-
Note that parsing hashes per default does [require string keys](#symbol-keys
|
88
|
-
|
89
|
-
## Extend vs. Decorator
|
90
|
-
|
91
|
-
If you don't want representer modules to be mixed into your objects (using `#extend`) you can use the `Decorator` strategy [described below](#decorator-vs-extend). Decorating instead of extending was introduced in 1.4.
|
92
|
-
|
93
|
-
|
94
|
-
## Aliasing
|
95
|
-
|
96
|
-
If your property name doesn't match the name in the document, use the `:as` option.
|
97
|
-
|
98
|
-
```ruby
|
99
|
-
module SongRepresenter
|
100
|
-
include Representable::JSON
|
101
|
-
|
102
|
-
property :title, as: :name
|
103
|
-
property :track
|
104
|
-
end
|
105
|
-
|
106
|
-
song.to_json #=> {"name":"Fallout","track":1}
|
107
|
-
```
|
108
|
-
|
109
|
-
|
110
|
-
## Wrapping
|
111
|
-
|
112
|
-
Let the representer know if you want wrapping.
|
113
|
-
|
114
|
-
```ruby
|
115
|
-
module SongRepresenter
|
116
|
-
include Representable::JSON
|
117
|
-
|
118
|
-
self.representation_wrap= :hit
|
119
|
-
|
120
|
-
property :title
|
121
|
-
property :track
|
122
|
-
end
|
123
|
-
```
|
124
|
-
|
125
|
-
This will add a container for rendering and consuming.
|
126
|
-
|
127
|
-
```ruby
|
128
|
-
song.extend(SongRepresenter).to_json
|
129
|
-
#=> {"hit":{"title":"Fallout","track":1}}
|
130
|
-
```
|
131
|
-
|
132
|
-
Setting `self.representation_wrap = true` will advice representable to figure out the wrap itself by inspecting the represented object class.
|
68
|
+
Note that parsing hashes per default does [require string keys](http://trailblazer.to/gems/representable/3.0/api.html#symbol-keys) and does _not_ pick up symbol keys.
|
133
69
|
|
134
70
|
|
135
71
|
## Collections
|
@@ -137,7 +73,7 @@ Setting `self.representation_wrap = true` will advice representable to figure ou
|
|
137
73
|
Let's add a list of composers to the song representation.
|
138
74
|
|
139
75
|
```ruby
|
140
|
-
|
76
|
+
class SongRepresenter < Representable::Decorator
|
141
77
|
include Representable::JSON
|
142
78
|
|
143
79
|
property :title
|
@@ -172,143 +108,20 @@ album = Album.new(name: "The Police", songs: [song, Song.new(title: "Synchronici
|
|
172
108
|
Here comes the representer that defines the composition.
|
173
109
|
|
174
110
|
```ruby
|
175
|
-
|
111
|
+
class AlbumRepresenter < Representable::Decorator
|
176
112
|
include Representable::JSON
|
177
113
|
|
178
114
|
property :name
|
179
|
-
collection :songs,
|
180
|
-
end
|
181
|
-
```
|
182
|
-
|
183
|
-
Note that nesting works with both plain `#property` and `#collection`.
|
184
|
-
|
185
|
-
When rendering, the `:extend` module is used to extend the attribute(s) with the correct representer module.
|
186
|
-
|
187
|
-
```ruby
|
188
|
-
album.extend(AlbumRepresenter).to_json
|
189
|
-
#=> {"name":"The Police","songs":[{"title":"Fallout","composers":["Stewart Copeland","Sting"]},{"title":"Synchronicity","composers":[]}]}
|
190
|
-
```
|
191
|
-
|
192
|
-
Parsing a documents needs both `:extend` and the `:class` option as the parser requires knowledge what kind of object to create from the nested composition.
|
193
|
-
|
194
|
-
```ruby
|
195
|
-
Album.new.extend(AlbumRepresenter).
|
196
|
-
from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]}})
|
197
|
-
|
198
|
-
#=> #<Album name="Offspring", songs=[#<Song title="Genocide">, #<Song title="Nitro", composers=["Offspring"]>]>
|
199
|
-
```
|
200
|
-
|
201
|
-
## Suppressing Nested Wraps
|
202
|
-
|
203
|
-
When reusing a representer for a nested document, you might want to suppress the wrap for the nested fragment.
|
204
|
-
|
205
|
-
```ruby
|
206
|
-
module SongRepresenter
|
207
|
-
include Representable::JSON
|
208
|
-
|
209
|
-
self.representation_wrap = :songs
|
210
|
-
property :title
|
211
|
-
end
|
212
|
-
```
|
213
|
-
|
214
|
-
When reusing `SongRepresenter` in a nested setup you can suppress the wrapping using the `:wrap` option.
|
215
|
-
|
216
|
-
```ruby
|
217
|
-
module AlbumRepresenter
|
218
|
-
include Representable::JSON
|
219
|
-
|
220
|
-
collection :songs, extend: SongRepresenter, wrap: false
|
115
|
+
collection :songs, decorator: SongRepresenter, class: Song
|
221
116
|
end
|
222
117
|
```
|
223
118
|
|
224
|
-
The `representation_wrap` from the nested representer now won't be rendered and parsed.
|
225
|
-
|
226
|
-
```ruby
|
227
|
-
album.extend(AlbumRepresenter).to_json #=> "{\"songs\": [{\"name\": \"Roxanne\"}]}"
|
228
|
-
```
|
229
|
-
|
230
|
-
Note that this only works for JSON and Hash at the moment.
|
231
|
-
|
232
|
-
|
233
|
-
## Parse Strategies
|
234
|
-
|
235
|
-
When parsing `collection`s (also applies to single `property`s), representable usually iterates the incoming list and creates a new object per array item.
|
236
|
-
|
237
|
-
Parse strategies let you do that manually.
|
238
|
-
|
239
|
-
```ruby
|
240
|
-
collection :songs, :parse_strategy => lambda { |fragment, i, options|
|
241
|
-
songs << song = Song.new
|
242
|
-
song
|
243
|
-
}
|
244
|
-
```
|
245
|
-
|
246
|
-
The above code will *add* a new `Song` per incoming item. Each instance will still be extended and populated with attributes (note that you can [change that](#skipping-rendering-or-parsing) as well).
|
247
|
-
|
248
|
-
This gives you all the freedom you need for your nested parsing.
|
249
|
-
|
250
|
-
|
251
|
-
## Syncing Objects
|
252
|
-
|
253
|
-
Usually, representable creates a new nested object when parsing. If you want to update an existing object, use the `parse_strategy: :sync` option.
|
254
|
-
|
255
|
-
```ruby
|
256
|
-
module AlbumRepresenter
|
257
|
-
include Representable::JSON
|
258
|
-
|
259
|
-
collection :songs, extend: SongRepresenter, parse_strategy: :sync
|
260
|
-
```
|
261
|
-
|
262
|
-
When parsing an album, it will now call `from_json` on the existing songs in the collection.
|
263
|
-
|
264
|
-
```ruby
|
265
|
-
album = Album.find(1)
|
266
|
-
album.songs.first #=> #<Song:0x999 title: "Panama">
|
267
|
-
```
|
268
|
-
|
269
|
-
Note that the album already contains a song instance.
|
270
|
-
|
271
|
-
```ruby
|
272
|
-
album.extend(AlbumRepresenter).
|
273
|
-
from_json('{songs: [{title: "Eruption"}]}')
|
274
|
-
|
275
|
-
album.songs.first #=> #<Song:0x999 title: "Eruption">
|
276
|
-
```
|
277
|
-
|
278
|
-
Now, representable didn't create a new `Song` instance but updated the existing, resulting in renaming the song.
|
279
|
-
|
280
|
-
|
281
|
-
## Find-or-Create For Incoming Objects
|
282
|
-
|
283
|
-
Representable comes with another strategy called `:find_or_instantiate` which allows creating a property or collection from the incoming document.
|
284
|
-
|
285
|
-
Consider the following incoming hash.
|
286
|
-
|
287
|
-
```ruby
|
288
|
-
{"songs" => [{"id" => 1, "title" => "American Paradox"}, {"title" => "Uncoil"}}
|
289
|
-
```
|
290
|
-
|
291
|
-
And this representer setup.
|
292
|
-
|
293
|
-
```ruby
|
294
|
-
collection :songs, class: Song, parse_strategy: :find_or_instantiate
|
295
|
-
```
|
296
|
-
|
297
|
-
In `album.from_hash(..)`, representable will try to call `Song.find(1)` for the first `songs` collection element and `Song.new` for the second (as it doesn't has any `id`), resulting in an array of two `Song` instances, the first an existing, the second a new object.
|
298
|
-
|
299
|
-
**Note**: the various parsing strategies are a collection of "best practices" people find useful. Such a strategy is basically just a set of configuration options, mainly utilizing the `:instance` option.
|
300
|
-
|
301
|
-
Check out the `ParsingStrategy` module to write your own strategy. If you find it useful, please commit it to the core library (with tests).
|
302
|
-
|
303
|
-
The current state of the `:find_or_instantiate` strategy is subject to change.
|
304
|
-
|
305
|
-
|
306
119
|
## Inline Representers
|
307
120
|
|
308
121
|
If you don't want to maintain two separate modules when nesting representations you can define the `SongRepresenter` inline.
|
309
122
|
|
310
123
|
```ruby
|
311
|
-
|
124
|
+
class AlbumRepresenter < Representable::Decorator
|
312
125
|
include Representable::JSON
|
313
126
|
|
314
127
|
property :name
|
@@ -320,1154 +133,43 @@ module AlbumRepresenter
|
|
320
133
|
end
|
321
134
|
```
|
322
135
|
|
323
|
-
|
324
|
-
|
325
|
-
An inline representer is just a Ruby module (or a `Decorator` class). You can include other representer modules. This is handy when having a base representer that needs to be extended in the inline block.
|
326
|
-
|
327
|
-
```ruby
|
328
|
-
module AlbumRepresenter
|
329
|
-
include Representable::JSON
|
330
|
-
|
331
|
-
property :hit do
|
332
|
-
include SongRepresenter
|
333
|
-
|
334
|
-
property :numbers_sold
|
335
|
-
end
|
336
|
-
```
|
337
|
-
|
338
|
-
If you need to include modules in all inline representers automatically, register it as a feature.
|
339
|
-
|
340
|
-
```ruby
|
341
|
-
module AlbumRepresenter
|
342
|
-
include Representable::JSON
|
343
|
-
feature Link # imports ::link
|
344
|
-
|
345
|
-
link "/album/1"
|
346
|
-
|
347
|
-
property :hit do
|
348
|
-
link "/hit/1" # link method imported automatically.
|
349
|
-
end
|
350
|
-
```
|
351
|
-
|
352
|
-
|
353
|
-
## Representing Singular Models And Collections
|
354
|
-
|
355
|
-
You can explicitly define representers for collections of models using a ["Lonely Collection"](#lonely-collections). Or you can let representable do that for you.
|
356
|
-
|
357
|
-
Rendering a collection of objects comes for free, using `::for_collection`.
|
358
|
-
|
359
|
-
```ruby
|
360
|
-
Song.all.extend(SongRepresenter.for_collection).to_hash
|
361
|
-
#=> [{title: "Sevens"}, {title: "Eric"}]
|
362
|
-
```
|
363
|
-
|
364
|
-
For parsing, you need to provide the class constant to which the items should be deserialized to.
|
365
|
-
|
366
|
-
```ruby
|
367
|
-
module SongRepresenter
|
368
|
-
include Representable::Hash
|
369
|
-
property :title
|
370
|
-
|
371
|
-
collection_representer class: Song
|
372
|
-
end
|
373
|
-
```
|
374
|
-
|
375
|
-
You can now parse collections to `Song` instances.
|
376
|
-
|
377
|
-
```ruby
|
378
|
-
[].extend(SongRepresenter.for_collection).from_hash([{title: "Sevens"}, {title: "Eric"}])
|
379
|
-
```
|
380
|
-
|
381
|
-
As always, this works for decorators _and_ modules.
|
382
|
-
|
383
|
-
In case you don't want to know whether or not you're working with a collection or singular model, use `::represent`.
|
384
|
-
|
385
|
-
```ruby
|
386
|
-
# singular
|
387
|
-
SongRepresenter.represent(Song.find(1)).to_hash #=> {title: "Sevens"}
|
388
|
-
|
389
|
-
# collection
|
390
|
-
SongRepresenter.represent(Song.all).to_hash #=> [{title: "Sevens"}, {title: "Eric"}]
|
391
|
-
```
|
392
|
-
|
393
|
-
As you can see, `::represent` figures out the correct representer for you (works also for parsing!).
|
394
|
-
|
395
|
-
Note: the implicit collection representer internally is implemented using a lonely collection. Everything you pass to `::collection_representer` is simply provided to the `::items` call in the lonely collection. That allows you to use `:parse_strategy` and all the other goodies, too.
|
396
|
-
|
397
|
-
|
398
|
-
## Document Nesting
|
399
|
-
|
400
|
-
Not always does the structure of the desired document map to your objects. The `::nested` method allows you to structure properties in a separate section while still mapping the properties to the outer object.
|
401
|
-
|
402
|
-
Imagine the following document.
|
403
|
-
|
404
|
-
```json
|
405
|
-
{"title": "Roxanne",
|
406
|
-
"details":
|
407
|
-
{"track": 3,
|
408
|
-
"length": "4:10"}
|
409
|
-
}
|
410
|
-
```
|
411
|
-
|
412
|
-
However, both `track` and `length` are properties of the song object `<Song#0x999 title: "Roxanne", track: 3 ...>`, there is no such concept as `details` in the `Song` class. Representable gives you `::nested` to achieve this.
|
413
|
-
|
414
|
-
```ruby
|
415
|
-
class SongRepresenter < Representable::Decorator
|
416
|
-
include Representable::JSON
|
417
|
-
|
418
|
-
property :title
|
419
|
-
|
420
|
-
nested :details do
|
421
|
-
property :track
|
422
|
-
property :length
|
423
|
-
end
|
424
|
-
end
|
425
|
-
```
|
426
|
-
|
427
|
-
Just use an inline representer or the `extend:` option to define nested properties. Accessors for nested properties will still be called on the outer object (here, `song`). And as always, this works both ways for rendering and parsing.
|
428
|
-
|
429
|
-
Note that `::nested` internally is implemented using `Decorator`. When adding methods inside the `nested` block, make sure to use `represented` (`self` will point to the decorator instance).
|
430
|
-
|
431
|
-
|
432
|
-
## Decorator vs. Extend
|
433
|
-
|
434
|
-
People who dislike `:extend` go use the `Decorator` strategy!
|
435
|
-
|
436
|
-
```ruby
|
437
|
-
class SongRepresentation < Representable::Decorator
|
438
|
-
include Representable::JSON
|
439
|
-
|
440
|
-
property :title
|
441
|
-
property :track
|
442
|
-
end
|
443
|
-
```
|
444
|
-
|
445
|
-
The `Decorator` constructor requires the represented object.
|
446
|
-
|
447
|
-
```ruby
|
448
|
-
SongRepresentation.new(song).to_json
|
449
|
-
```
|
450
|
-
|
451
|
-
This will leave the `song` instance untouched as the decorator just uses public accessors to represent the hit.
|
452
|
-
|
453
|
-
In compositions you need to specify the decorators for the nested items using the `:decorator` option where you'd normally use `:extend`.
|
454
|
-
|
455
|
-
```ruby
|
456
|
-
class AlbumRepresentation < Representable::Decorator
|
457
|
-
include Representable::JSON
|
458
|
-
|
459
|
-
collection :songs, :class => Song, :decorator => SongRepresentation
|
460
|
-
end
|
461
|
-
```
|
462
|
-
|
463
|
-
### Methods In Modules
|
464
|
-
|
465
|
-
You can define methods in representers in case they aren't defined on the represented object.
|
466
|
-
|
467
|
-
```ruby
|
468
|
-
module SongRepresenter
|
469
|
-
property :title
|
470
|
-
|
471
|
-
def title
|
472
|
-
@name
|
473
|
-
end
|
474
|
-
```
|
475
|
-
|
476
|
-
That works as the method is mixed into the represented object.
|
136
|
+
## More
|
477
137
|
|
478
|
-
|
138
|
+
Representable has many more features and can literally parse and render any kind of document to an arbitrary Ruby object graph.
|
479
139
|
|
480
|
-
|
481
|
-
property :song do
|
482
|
-
property :title
|
483
|
-
|
484
|
-
def title
|
485
|
-
"Static titles are better"
|
486
|
-
end
|
487
|
-
end
|
488
|
-
```
|
489
|
-
|
490
|
-
### Methods In Decorators
|
491
|
-
|
492
|
-
When adding a method to a decorator, representable will still invoke accessors on the represented instance - unless you tell it the scope.
|
493
|
-
|
494
|
-
```ruby
|
495
|
-
class SongRepresenter < Representable::Decorator
|
496
|
-
property :title, exec_context: :decorator
|
497
|
-
|
498
|
-
def title
|
499
|
-
represented.name
|
500
|
-
end
|
501
|
-
end
|
502
|
-
```
|
140
|
+
Please check the [official documentation for more](http://trailblazer.to/gems/representable/).
|
503
141
|
|
504
|
-
This will call `title` getter and setter on the decorator instance, not on the represented object. You can still access the represented object in the decorator method using `represented`. BTW, in a module representer this option setting is ignored.
|
505
142
|
|
506
|
-
|
143
|
+
## Installation
|
507
144
|
|
508
|
-
|
145
|
+
The representable gem runs with all Ruby versions >= 1.9.3.
|
509
146
|
|
510
147
|
```ruby
|
511
|
-
|
512
|
-
property :title, getter: lambda { |*| @name }
|
148
|
+
gem 'representable'
|
513
149
|
```
|
514
|
-
As always, the block is executed in the represented object's context.
|
515
150
|
|
151
|
+
### Dependencies
|
516
152
|
|
517
|
-
|
153
|
+
Representable does a great job with JSON, it also features support for XML, YAML and pure ruby
|
154
|
+
hashes. But Representable did not bundle dependencies for JSON and XML.
|
518
155
|
|
519
|
-
|
156
|
+
If you want to use JSON, add the following to your Gemfile:
|
520
157
|
|
521
158
|
```ruby
|
522
|
-
|
159
|
+
gem 'multi_json'
|
523
160
|
```
|
524
161
|
|
525
|
-
If you want to
|
162
|
+
If you want to use XML, add the following to your Gemfile:
|
526
163
|
|
527
164
|
```ruby
|
528
|
-
|
529
|
-
include Representable::JSON
|
530
|
-
|
531
|
-
property :title, :getter => lambda { |args| title + args[:append] }
|
532
|
-
end
|
165
|
+
gem 'nokogiri'
|
533
166
|
```
|
534
167
|
|
535
|
-
Note that the block is executed in the represented model context which allows using accessors and instance variables.
|
536
|
-
|
537
|
-
|
538
|
-
The same works for parsing using the `:setter` method.
|
539
|
-
|
540
|
-
```ruby
|
541
|
-
property :title, :setter => lambda { |val, args| self.title= val + args[:append] }
|
542
|
-
```
|
543
|
-
|
544
|
-
Here, the block retrieves two arguments: the parsed value and your user options.
|
545
|
-
|
546
|
-
You can also use the `:getter` option instead of writing a reader method. Even when you're not interested in user options you can still use this technique.
|
547
|
-
|
548
|
-
```ruby
|
549
|
-
property :title, :getter => lambda { |*| @name }
|
550
|
-
```
|
551
|
-
|
552
|
-
This hash will also be available in the `:if` block, documented [here](https://github.com/apotonick/representable/#conditions) and will be passed to nested objects.
|
553
|
-
|
554
|
-
|
555
|
-
## Dynamic Options
|
556
|
-
|
557
|
-
Most of `property`'s options are dynamic, meaning the can be either a static value, a lambda or a :symbol refering to an instance method to be called.
|
558
|
-
|
559
|
-
All user options are passed to the lambdas, e.g. when you call
|
560
|
-
|
561
|
-
```ruby
|
562
|
-
song.to_hash(volume: 9)
|
563
|
-
```
|
564
|
-
|
565
|
-
the lambda invocation for `:as` would look like this.
|
566
|
-
|
567
|
-
```ruby
|
568
|
-
property :name, as: lambda do |args|
|
569
|
-
args #=> {:volume=>9}
|
570
|
-
end
|
571
|
-
```
|
572
|
-
|
573
|
-
### Available Options
|
574
|
-
|
575
|
-
Here's a list of all dynamic options and their argument signature.
|
576
|
-
|
577
|
-
* `as: lambda { |args| }` ([see Aliasing](#aliasing))
|
578
|
-
* `getter: lambda { |args| }` ([see docs](#passing-options))
|
579
|
-
* `setter: lambda { |value, args| }` ([see docs](#passing-options))
|
580
|
-
* `class: lambda { |fragment, [i], args| }` ([see Nesting](#nesting))
|
581
|
-
* `extend: lambda { |object, args| }` ([see Nesting](#nesting))
|
582
|
-
* `instance: lambda { |fragment, [i], args| }` ([see Object Creation](#polymorphic-object-creation))
|
583
|
-
* `reader: lambda { |document, args| }` ([see Read And Write](#overriding-read-and-write))
|
584
|
-
* `writer: lambda { |document, args| }` ([see Read And Write](#overriding-read-and-write))
|
585
|
-
* `skip_parse: lambda { |fragment, args| }` ([see Skip Parsing](#skip-parsing))
|
586
|
-
* `skip_render: lambda { |object, args| }` ([see Skip Rendering](#skip-rendering))
|
587
|
-
* `parse_filter: lambda { |fragment, document, args| }` ([see Filters](#filters)))
|
588
|
-
* `render_filter: lambda { |value, document, args| }` ([see Filters](#filters))
|
589
|
-
* `if: lambda { |args| }` ([see Conditions](#conditions))
|
590
|
-
* `prepare: lambda { |object, args| }` ([see docs](#rendering-and-parsing-without-extend))
|
591
|
-
* `serialize: lambda { |object, args| }` ([see docs](#overriding-serialize-and-deserialize))
|
592
|
-
* `deserialize: lambda { |object, fragment, args| }` ([see docs](#overriding-serialize-and-deserialize))
|
593
|
-
* `representation_wrap` is a dynamic option, too: `self.representation_wrap = lambda do { |args| }` ([see Wrapping](#wrapping))
|
594
|
-
|
595
|
-
|
596
|
-
### Option Arguments
|
597
|
-
|
598
|
-
The `pass_options: true` option instructs representable to pass a special `Options` instance into lambdas or methods. This is handy if you need access to the other stakeholder objects involved in representing objects.
|
599
|
-
|
600
|
-
```ruby
|
601
|
-
property :title, pass_options: true, getter: lambda do |args|
|
602
|
-
args #=> <#Options>
|
603
|
-
args.binding # etc.
|
604
|
-
end
|
605
|
-
```
|
606
|
-
|
607
|
-
The `Options` instance exposes the following readers: `#binding`, `#represented`, `#decorator` and `#user_options` which is the hash you usually have as `args`.
|
608
|
-
|
609
|
-
Option-specific arguments (e.g. `fragment`, [see here](#available-options)) are still prepended, making the `Options` object always the *last* argument.
|
610
|
-
|
611
|
-
|
612
|
-
## Filters
|
613
|
-
|
614
|
-
Representabe offers you `:render_filter` and `:parse_filter` to modify the value to be rendered or parsed.
|
615
|
-
|
616
|
-
Filters are implemented using `Pipeline`, which means you can add as many as you want. The result from the former filter will be passed to the next.
|
617
|
-
|
618
|
-
```ruby
|
619
|
-
property :title, render_filter: lambda { |value, doc, *args| value.html_safe }
|
620
|
-
```
|
621
|
-
|
622
|
-
This will be executed right before the fragment gets rendered into the document.
|
623
|
-
|
624
|
-
```ruby
|
625
|
-
property :title, parse_filter: lambda { |fragment, doc, *args| Sanitizer.call(fragment) }
|
626
|
-
```
|
627
|
-
|
628
|
-
Just before setting the fragment to the object via the `:setter`, the `:parse_filter` is called.
|
629
|
-
|
630
|
-
|
631
|
-
## Skip Parsing
|
632
|
-
|
633
|
-
You can skip parsing for particular fragments which will completely ignore them as if they weren't present in the parsed document.
|
634
|
-
|
635
|
-
```ruby
|
636
|
-
property :title, skip_parse: lambda { |fragment, options| fragment.blank? }
|
637
|
-
```
|
638
|
-
|
639
|
-
Note that when used with collections, this is evaluated per item.
|
640
|
-
|
641
|
-
```ruby
|
642
|
-
collection :songs, skip_parse: lambda { |fragment, options| fragment["title"].blank? } do
|
643
|
-
property :title
|
644
|
-
end
|
645
|
-
```
|
646
|
-
|
647
|
-
This won't parse empty incoming songs in the collection.
|
648
|
-
|
649
|
-
## Skip Rendering
|
650
|
-
|
651
|
-
The exact same also works for rendering. You can skip rendering properties and items of collections.
|
652
|
-
|
653
|
-
```ruby
|
654
|
-
property :title, skip_render: lambda { |object, options| options[:skip_title] == true }
|
655
|
-
```
|
656
|
-
|
657
|
-
In collections, this will be run per item.
|
658
|
-
|
659
|
-
|
660
|
-
## Callable Options
|
661
|
-
|
662
|
-
While lambdas are one option for dynamic options, you might also pass a "callable" object to a directive.
|
663
|
-
|
664
|
-
```ruby
|
665
|
-
class Sanitizer
|
666
|
-
include Uber::Callable
|
667
|
-
|
668
|
-
def call(represented, fragment, doc, *args)
|
669
|
-
fragment.sanitize
|
670
|
-
end
|
671
|
-
end
|
672
|
-
```
|
673
|
-
|
674
|
-
Note how including `Uber::Callable` marks instances of this class as callable. No `respond_to?` or other magic takes place here.
|
675
|
-
|
676
|
-
```ruby
|
677
|
-
property :title, parse_filter: Santizer.new
|
678
|
-
```
|
679
|
-
|
680
|
-
This is enough to have the `Sanitizer` class run with all the arguments that are usually passed to the lambda (preceded by the represented object as first argument).
|
681
|
-
|
682
|
-
|
683
|
-
## XML Support
|
684
|
-
|
685
|
-
While representable does a great job with JSON, it also features support for XML, YAML and pure ruby hashes.
|
686
|
-
|
687
|
-
```ruby
|
688
|
-
require 'representable/xml'
|
689
|
-
|
690
|
-
module SongRepresenter
|
691
|
-
include Representable::XML
|
692
|
-
|
693
|
-
property :title
|
694
|
-
property :track
|
695
|
-
collection :composers
|
696
|
-
end
|
697
|
-
```
|
698
|
-
|
699
|
-
For XML we just include the `Representable::XML` module.
|
700
|
-
|
701
|
-
```xml
|
702
|
-
Song.new(title: "Fallout", composers: ["Stewart Copeland", "Sting"]).
|
703
|
-
extend(SongRepresenter).to_xml #=>
|
704
|
-
<song>
|
705
|
-
<title>Fallout</title>
|
706
|
-
<composers>Stewart Copeland</composers>
|
707
|
-
<composers>Sting</composers>
|
708
|
-
</song>
|
709
|
-
```
|
710
|
-
|
711
|
-
|
712
|
-
## Using Helpers
|
713
|
-
|
714
|
-
Sometimes it's useful to override accessors to customize output or parsing.
|
715
|
-
|
716
|
-
```ruby
|
717
|
-
module AlbumRepresenter
|
718
|
-
include Representable::JSON
|
719
|
-
|
720
|
-
property :name
|
721
|
-
collection :songs
|
722
|
-
|
723
|
-
def name
|
724
|
-
super.upcase
|
725
|
-
end
|
726
|
-
end
|
727
|
-
|
728
|
-
Album.new(:name => "The Police").
|
729
|
-
extend(AlbumRepresenter).to_json
|
730
|
-
|
731
|
-
#=> {"name":"THE POLICE","songs":[]}
|
732
|
-
```
|
733
|
-
|
734
|
-
Note how the representer allows calling `super` in order to access the original attribute method of the represented object.
|
735
|
-
|
736
|
-
To change the parsing process override the setter.
|
737
|
-
|
738
|
-
```ruby
|
739
|
-
def name=(value)
|
740
|
-
super(value.downcase)
|
741
|
-
end
|
742
|
-
```
|
743
|
-
|
744
|
-
## Inheritance
|
745
|
-
|
746
|
-
To reuse existing representers use inheritance.
|
747
|
-
|
748
|
-
Inheritance works by `include`ing already defined representers.
|
749
|
-
|
750
|
-
```ruby
|
751
|
-
module CoverSongRepresenter
|
752
|
-
include Representable::JSON
|
753
|
-
include SongRepresenter
|
754
|
-
|
755
|
-
property :copyright
|
756
|
-
end
|
757
|
-
```
|
758
|
-
|
759
|
-
This results in a representer with the following properties.
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
```ruby
|
765
|
-
property :title # inherited from SongRepresenter.
|
766
|
-
property :copyright
|
767
|
-
```
|
768
|
-
|
769
|
-
With decorators, you - surprisingly - use class inheritance.
|
770
|
-
|
771
|
-
```ruby
|
772
|
-
class HitRepresenter < SongRepresenter
|
773
|
-
collection :airplays
|
774
|
-
```
|
775
|
-
|
776
|
-
|
777
|
-
## Overriding Properties
|
778
|
-
|
779
|
-
You might want to override a particular property in an inheriting representer. Successively calling `property(name)` will override the former definition for `name` just as you know it from overriding methods.
|
780
|
-
|
781
|
-
```ruby
|
782
|
-
module CoverSongRepresenter
|
783
|
-
include Representable::JSON
|
784
|
-
|
785
|
-
include SongRepresenter # defines property :title
|
786
|
-
property :title, as: :known_as # overrides that definition.
|
787
|
-
end
|
788
|
-
```
|
789
|
-
|
790
|
-
This behaviour was added in 1.7.
|
791
|
-
|
792
|
-
|
793
|
-
## Partly Overriding Properties
|
794
|
-
|
795
|
-
If you wanna override only certain options of the property, use `:inherit`.
|
796
|
-
|
797
|
-
```ruby
|
798
|
-
module SongRepresenter
|
799
|
-
include Representable::JSON
|
800
|
-
|
801
|
-
property :title, as: :known_as
|
802
|
-
end
|
803
|
-
```
|
804
|
-
|
805
|
-
You can now inherit properties but still override or add options.
|
806
|
-
|
807
|
-
```ruby
|
808
|
-
module CoverSongRepresenter
|
809
|
-
include Representable::JSON
|
810
|
-
include SongRepresenter
|
811
|
-
|
812
|
-
property :title, getter: lambda { Title.random }, inherit: true
|
813
|
-
end
|
814
|
-
```
|
815
|
-
|
816
|
-
Using the `:inherit`, this will result in a property having the following options.
|
817
|
-
|
818
|
-
```ruby
|
819
|
-
property :title,
|
820
|
-
as: :known_as, # inherited from SongRepresenter.
|
821
|
-
getter: lambda { .. } # added in inheriting representer.
|
822
|
-
```
|
823
|
-
|
824
|
-
## Inheritance With Inline Representers
|
825
|
-
|
826
|
-
Inheriting also works for inline representers.
|
827
|
-
|
828
|
-
```ruby
|
829
|
-
module SongRepresenter
|
830
|
-
include Representable::JSON
|
831
|
-
|
832
|
-
property :title
|
833
|
-
property :label do
|
834
|
-
property :name
|
835
|
-
end
|
836
|
-
end
|
837
|
-
```
|
838
|
-
|
839
|
-
You can now override or add properties with the inline representer.
|
840
|
-
|
841
|
-
```ruby
|
842
|
-
module HitRepresenter
|
843
|
-
include Representable::JSON
|
844
|
-
include SongRepresenter
|
845
|
-
|
846
|
-
property :label, inherit: true do
|
847
|
-
property :country
|
848
|
-
end
|
849
|
-
end
|
850
|
-
```
|
851
|
-
|
852
|
-
Results in a combined inline representer as it inherits.
|
853
|
-
|
854
|
-
```ruby
|
855
|
-
property :label do
|
856
|
-
property :name
|
857
|
-
property :country
|
858
|
-
end
|
859
|
-
```
|
860
|
-
|
861
|
-
Naturally, `:inherit` can be used within the inline representer block.
|
862
|
-
|
863
|
-
Note that the following also works.
|
864
|
-
|
865
|
-
```ruby
|
866
|
-
module HitRepresenter
|
867
|
-
include Representable::JSON
|
868
|
-
include SongRepresenter
|
869
|
-
|
870
|
-
property :label, as: :company, inherit: true
|
871
|
-
end
|
872
|
-
```
|
873
|
-
|
874
|
-
This renames the property but still inherits all the inlined configuration.
|
875
|
-
|
876
|
-
Basically, `:inherit` copies the configuration from the parent property, then merges in your options from the inheriting representer. It exposes the same behaviour as `super` in Ruby - when using `:inherit` the property must exist in the parent representer.
|
877
|
-
|
878
|
-
## Polymorphic Extend
|
879
|
-
|
880
|
-
Sometimes heterogenous collections of objects from different classes must be represented. Or you don't know which representer to use at compile-time and need to delay the computation until runtime. This is why `:extend` accepts a lambda, too.
|
881
|
-
|
882
|
-
Given we not only have songs, but also cover songs.
|
883
|
-
|
884
|
-
```ruby
|
885
|
-
class CoverSong < Song
|
886
|
-
end
|
887
|
-
```
|
888
|
-
|
889
|
-
And a non-homogenous collection of songs.
|
890
|
-
|
891
|
-
```ruby
|
892
|
-
songs = [ Song.new(title: "Weirdo", track: 5),
|
893
|
-
CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")]
|
894
|
-
|
895
|
-
album = Album.new(name: "Incognito", songs: songs)
|
896
|
-
```
|
897
|
-
|
898
|
-
The `CoverSong` instances are to be represented by their very own `CoverSongRepresenter` defined above. We can't just use a static module in the `:extend` option, so go use a dynamic lambda!
|
899
|
-
|
900
|
-
```ruby
|
901
|
-
module AlbumRepresenter
|
902
|
-
include Representable::JSON
|
903
|
-
|
904
|
-
property :name
|
905
|
-
collection :songs, :extend => lambda { |song, *| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }
|
906
|
-
end
|
907
|
-
```
|
908
|
-
|
909
|
-
Note that the lambda block is evaluated in the represented object context which allows to access helpers or whatever in the block. This works for single properties, too.
|
910
|
-
|
911
|
-
|
912
|
-
## Polymorphic Object Creation
|
913
|
-
|
914
|
-
Rendering heterogenous collections usually implies that you also need to parse those. Luckily, `:class` also accepts a lambda.
|
915
|
-
|
916
|
-
```ruby
|
917
|
-
module AlbumRepresenter
|
918
|
-
include Representable::JSON
|
919
|
-
|
920
|
-
property :name
|
921
|
-
collection :songs,
|
922
|
-
:extend => ...,
|
923
|
-
:class => lambda { |hsh, *| hsh.has_key?("copyright") ? CoverSong : Song }
|
924
|
-
end
|
925
|
-
```
|
926
|
-
|
927
|
-
The block for `:class` receives the currently parsed fragment. Here, this might be something like `{"title"=>"Weirdo", "track"=>5}`.
|
928
|
-
|
929
|
-
If this is not enough, you may override the entire object creation process using `:instance`.
|
930
|
-
|
931
|
-
```ruby
|
932
|
-
module AlbumRepresenter
|
933
|
-
include Representable::JSON
|
934
|
-
|
935
|
-
property :name
|
936
|
-
collection :songs,
|
937
|
-
:extend => ...,
|
938
|
-
:instance => lambda { |hsh, *| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) }
|
939
|
-
end
|
940
|
-
```
|
941
|
-
|
942
|
-
## Hashes
|
943
|
-
|
944
|
-
As an addition to single properties and collections representable also offers to represent hash attributes.
|
945
|
-
|
946
|
-
```ruby
|
947
|
-
module SongRepresenter
|
948
|
-
include Representable::JSON
|
949
|
-
|
950
|
-
property :title
|
951
|
-
hash :ratings
|
952
|
-
end
|
953
|
-
|
954
|
-
Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).
|
955
|
-
extend(SongRepresenter).to_json
|
956
|
-
|
957
|
-
#=> {"title":"Bliss","ratings":{"Rolling Stone":4.9,"FryZine":4.5}}
|
958
|
-
```
|
959
|
-
|
960
|
-
## Lonely Hashes
|
961
|
-
|
962
|
-
Need to represent a bare hash without any container? Use the `JSON::Hash` representer (or XML::Hash).
|
963
|
-
|
964
|
-
```ruby
|
965
|
-
require 'representable/json/hash'
|
966
|
-
|
967
|
-
module FavoriteSongsRepresenter
|
968
|
-
include Representable::JSON::Hash
|
969
|
-
end
|
970
|
-
|
971
|
-
{"Nick" => "Hyper Music", "El" => "Blown In The Wind"}.extend(FavoriteSongsRepresenter).to_json
|
972
|
-
#=> {"Nick":"Hyper Music","El":"Blown In The Wind"}
|
973
|
-
```
|
974
|
-
|
975
|
-
Works both ways. The values are configurable and might be self-representing objects in turn. Tell the `Hash` by using `#values`.
|
976
|
-
|
977
|
-
```ruby
|
978
|
-
module FavoriteSongsRepresenter
|
979
|
-
include Representable::JSON::Hash
|
980
|
-
|
981
|
-
values extend: SongRepresenter, class: Song
|
982
|
-
end
|
983
|
-
|
984
|
-
{"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json
|
985
|
-
```
|
986
|
-
|
987
|
-
In XML, if you want to store hash attributes in tag attributes instead of dedicated nodes, use `XML::AttributeHash`.
|
988
|
-
|
989
|
-
## Lonely Collections
|
990
|
-
|
991
|
-
Same goes with arrays.
|
992
|
-
|
993
|
-
```ruby
|
994
|
-
require 'representable/json/collection'
|
995
|
-
|
996
|
-
module SongsRepresenter
|
997
|
-
include Representable::JSON::Collection
|
998
|
-
|
999
|
-
items extend: SongRepresenter, class: Song
|
1000
|
-
end
|
1001
|
-
```
|
1002
|
-
|
1003
|
-
The `#items` method lets you configure the contained entity representing here.
|
1004
|
-
|
1005
|
-
```ruby
|
1006
|
-
[Song.new(title: "Hyper Music"), Song.new(title: "Screenager")].extend(SongsRepresenter.for_collection).to_json
|
1007
|
-
#=> [{"title":"Hyper Music"},{"title":"Screenager"}]
|
1008
|
-
```
|
1009
|
-
|
1010
|
-
Note that this also works for XML.
|
1011
|
-
|
1012
|
-
|
1013
|
-
## YAML Support
|
1014
|
-
|
1015
|
-
Representable also comes with a YAML representer.
|
1016
|
-
|
1017
|
-
```ruby
|
1018
|
-
module SongRepresenter
|
1019
|
-
include Representable::YAML
|
1020
|
-
|
1021
|
-
property :title
|
1022
|
-
property :track
|
1023
|
-
collection :composers, :style => :flow
|
1024
|
-
end
|
1025
|
-
```
|
1026
|
-
|
1027
|
-
A nice feature is that `#collection` also accepts a `:style` option which helps having nicely formatted inline (or "flow") arrays in your YAML - if you want that!
|
1028
|
-
|
1029
|
-
```ruby
|
1030
|
-
song.extend(SongRepresenter).to_yaml
|
1031
|
-
#=>
|
1032
|
-
---
|
1033
|
-
title: Fallout
|
1034
|
-
composers: [Stewart Copeland, Sting]
|
1035
|
-
```
|
1036
|
-
|
1037
|
-
## More on XML
|
1038
|
-
|
1039
|
-
### Mapping Tag Attributes
|
1040
|
-
|
1041
|
-
You can also map properties to tag attributes in representable. This works only for the top-level node, though (seen from the representer's perspective).
|
1042
|
-
|
1043
|
-
```ruby
|
1044
|
-
module SongRepresenter
|
1045
|
-
include Representable::XML
|
1046
|
-
|
1047
|
-
property :title, attribute: true
|
1048
|
-
property :track, attribute: true
|
1049
|
-
end
|
1050
|
-
|
1051
|
-
Song.new(title: "American Idle").to_xml
|
1052
|
-
#=> <song title="American Idle" />
|
1053
|
-
```
|
1054
|
-
|
1055
|
-
Naturally, this works for both ways.
|
1056
|
-
|
1057
|
-
|
1058
|
-
### Mapping Content
|
1059
|
-
|
1060
|
-
The same concept can also be applied to content. If you need to map a property to the top-level node's content, use the `:content` option. Again, _top-level_ refers to the document fragment that maps to the representer.
|
1061
|
-
|
1062
|
-
```ruby
|
1063
|
-
module SongRepresenter
|
1064
|
-
include Representable::XML
|
1065
|
-
|
1066
|
-
property :title, content: true
|
1067
|
-
end
|
1068
|
-
|
1069
|
-
Song.new(title: "American Idle").to_xml
|
1070
|
-
#=> <song>American Idle</song>
|
1071
|
-
```
|
1072
|
-
|
1073
|
-
|
1074
|
-
### Wrapping Collections
|
1075
|
-
|
1076
|
-
It is sometimes unavoidable to wrap tag lists in a container tag.
|
1077
|
-
|
1078
|
-
```ruby
|
1079
|
-
module AlbumRepresenter
|
1080
|
-
include Representable::XML
|
1081
|
-
|
1082
|
-
collection :songs, :as => :song, :wrap => :songs
|
1083
|
-
end
|
1084
|
-
```
|
1085
|
-
|
1086
|
-
Note that `:wrap` defines the container tag name.
|
1087
|
-
|
1088
|
-
```xml
|
1089
|
-
Album.new.to_xml #=>
|
1090
|
-
<album>
|
1091
|
-
<songs>
|
1092
|
-
<song>Laundry Basket</song>
|
1093
|
-
<song>Two Kevins</song>
|
1094
|
-
<song>Wright and Rong</song>
|
1095
|
-
</songs>
|
1096
|
-
</album>
|
1097
|
-
```
|
1098
|
-
|
1099
|
-
### Namespaces
|
1100
|
-
|
1101
|
-
Support for namespaces are not yet implemented. However, if an incoming parsed document contains namespaces, you can automatically remove them.
|
1102
|
-
|
1103
|
-
```ruby
|
1104
|
-
module AlbumRepresenter
|
1105
|
-
include Representable::XML
|
1106
|
-
|
1107
|
-
remove_namespaces!
|
1108
|
-
```
|
1109
|
-
|
1110
|
-
## Avoiding Modules
|
1111
|
-
|
1112
|
-
There's been a rough discussion whether or not to use `extend` in Ruby. If you want to save that particular step when representing objects, define the representers right in your classes.
|
1113
|
-
|
1114
|
-
```ruby
|
1115
|
-
class Song < OpenStruct
|
1116
|
-
include Representable::JSON
|
1117
|
-
|
1118
|
-
property :name
|
1119
|
-
end
|
1120
|
-
```
|
1121
|
-
|
1122
|
-
I do not recommend this approach as it bloats your domain classes with representation logic that is barely needed elsewhere. Use [decorators](#decorator-vs-extend) instead.
|
1123
|
-
|
1124
|
-
|
1125
|
-
## More Options
|
1126
|
-
|
1127
|
-
Here's a quick overview about other available options for `#property` and its bro `#collection`.
|
1128
|
-
|
1129
|
-
|
1130
|
-
### Overriding Read And Write
|
1131
|
-
|
1132
|
-
This can be handy if a property needs to be compiled from several fragments. The lambda has access to the entire object document (either hash or `Nokogiri` node) and user options.
|
1133
|
-
|
1134
|
-
```ruby
|
1135
|
-
property :title, :writer => lambda { |doc, args| doc["title"] = title || original_title }
|
1136
|
-
```
|
1137
|
-
|
1138
|
-
When using the `:writer` option it is up to you to add fragments to the `doc` - representable won't add anything for this property.
|
1139
|
-
|
1140
|
-
The same works for parsing using `:reader`.
|
1141
|
-
|
1142
|
-
```ruby
|
1143
|
-
property :title, :reader => lambda { |doc, args| self.title = doc["title"] || doc["name"] }
|
1144
|
-
```
|
1145
|
-
|
1146
|
-
### Read/Write Restrictions
|
1147
|
-
|
1148
|
-
Using the `:readable` and `:writeable` options access to properties can be restricted.
|
1149
|
-
|
1150
|
-
```ruby
|
1151
|
-
property :title, :readable => false
|
1152
|
-
```
|
1153
|
-
|
1154
|
-
This will leave out the `title` property in the rendered document. Vice-versa, `:writeable` will skip the property when parsing and does not assign it.
|
1155
|
-
|
1156
|
-
|
1157
|
-
### Filtering
|
1158
|
-
|
1159
|
-
Representable also allows you to skip and include properties using the `:exclude` and `:include` options passed directly to the respective method.
|
1160
|
-
|
1161
|
-
```ruby
|
1162
|
-
song.to_json(:include => :title)
|
1163
|
-
#=> {"title":"Roxanne"}
|
1164
|
-
```
|
1165
|
-
|
1166
|
-
### Conditions
|
1167
|
-
|
1168
|
-
You can also define conditions on properties using `:if`, making them being considered only when the block returns a true value.
|
1169
|
-
|
1170
|
-
```ruby
|
1171
|
-
module SongRepresenter
|
1172
|
-
include Representable::JSON
|
1173
|
-
|
1174
|
-
property :title
|
1175
|
-
property :track, if: lambda { |args| track > 0 }
|
1176
|
-
end
|
1177
|
-
```
|
1178
|
-
|
1179
|
-
When rendering or parsing, the `track` property is considered only if track is valid. Note that the block is executed in instance context, giving you access to instance methods.
|
1180
|
-
|
1181
|
-
As always, the block retrieves your options. Given this render call
|
1182
|
-
|
1183
|
-
```ruby
|
1184
|
-
song.to_json(minimum_track: 2)
|
1185
|
-
```
|
1186
|
-
|
1187
|
-
your `:if` may process the options.
|
1188
|
-
|
1189
|
-
```ruby
|
1190
|
-
property :track, if: lambda { |opts| track > opts[:minimum_track] }
|
1191
|
-
```
|
1192
|
-
|
1193
|
-
### False And Nil Values
|
1194
|
-
|
1195
|
-
Since representable-1.2 `false` values _are_ considered when parsing and rendering. That particularly means properties that used to be unset (i.e. `nil`) after parsing might be `false` now. Vice versa, `false` properties that weren't included in the rendered document will be visible now.
|
1196
|
-
|
1197
|
-
If you want `nil` values to be included when rendering, use the `:render_nil` option.
|
1198
|
-
|
1199
|
-
```ruby
|
1200
|
-
property :track, render_nil: true
|
1201
|
-
```
|
1202
|
-
|
1203
|
-
### Empty Collections
|
1204
|
-
|
1205
|
-
Per default, empty collections are rendered (unless they're `nil`). You can suppress rendering.
|
1206
|
-
|
1207
|
-
```ruby
|
1208
|
-
collection :songs, render_empty: false
|
1209
|
-
```
|
1210
|
-
|
1211
|
-
This will be the default behaviour in 2.0.
|
1212
|
-
|
1213
|
-
|
1214
|
-
### Overriding Serialize And Deserialize
|
1215
|
-
|
1216
|
-
When serializing, the default mechanics after preparing the object are to call `object.to_hash`.
|
1217
|
-
|
1218
|
-
Override this step with `:serialize`.
|
1219
|
-
|
1220
|
-
```ruby
|
1221
|
-
property :song, extend: SongRepresenter,
|
1222
|
-
serialize: lambda { |object, *args| Marshal.dump(object) }
|
1223
|
-
```
|
1224
|
-
|
1225
|
-
Vice-versa, parsing allows the same.
|
1226
|
-
|
1227
|
-
```ruby
|
1228
|
-
property :song, extend: SongRepresenter,
|
1229
|
-
deserialize: lambda { |object, fragment, *args| Marshal.load(fragment) }
|
1230
|
-
```
|
1231
|
-
|
1232
|
-
|
1233
|
-
## Coercion
|
1234
|
-
|
1235
|
-
If you fancy coercion when parsing a document you can use the Coercion module which uses [virtus](https://github.com/solnic/virtus) for type conversion.
|
1236
|
-
|
1237
|
-
Include virtus in your Gemfile, first. Be sure to include virtus 0.5.0 or greater.
|
1238
|
-
|
1239
|
-
```ruby
|
1240
|
-
gem 'virtus', ">= 0.5.0"
|
1241
|
-
```
|
1242
|
-
|
1243
|
-
Use the `:type` option to specify the conversion target. Note that `:default` still works.
|
1244
|
-
|
1245
|
-
```ruby
|
1246
|
-
module SongRepresenter
|
1247
|
-
include Representable::JSON
|
1248
|
-
include Representable::Coercion
|
1249
|
-
|
1250
|
-
property :title
|
1251
|
-
property :recorded_at, :type => DateTime, :default => "May 12th, 2012"
|
1252
|
-
end
|
1253
|
-
```
|
1254
|
-
|
1255
|
-
In a decorator it works alike.
|
1256
|
-
|
1257
|
-
```ruby
|
1258
|
-
class SongRepresenter < Representable::Decorator
|
1259
|
-
include Representable::JSON
|
1260
|
-
include Representable::Coercion
|
1261
|
-
|
1262
|
-
property :recorded_at, :type => DateTime
|
1263
|
-
end
|
1264
|
-
```
|
1265
|
-
|
1266
|
-
Coercing values only happens when rendering or parsing a document. Representable does not create accessors in your model as `virtus` does.
|
1267
|
-
|
1268
|
-
|
1269
|
-
## Undocumented Features
|
1270
|
-
|
1271
|
-
*(Please don't read this section!)*
|
1272
|
-
|
1273
|
-
### Custom Binding
|
1274
|
-
|
1275
|
-
If you need a special binding for a property you're free to create it using the `:binding` option.
|
1276
|
-
|
1277
|
-
```ruby
|
1278
|
-
property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
|
1279
|
-
```
|
1280
|
-
|
1281
|
-
|
1282
|
-
### Syncing Parsing
|
1283
|
-
|
1284
|
-
You can use the parsed document fragment directly as a representable instance by returning `nil` in `:class`.
|
1285
|
-
|
1286
|
-
```ruby
|
1287
|
-
property :song, :class => lambda { |*| nil }
|
1288
|
-
```
|
1289
|
-
|
1290
|
-
This makes sense when your parsing looks like this.
|
1291
|
-
|
1292
|
-
```ruby
|
1293
|
-
hit.from_hash(song: <#Song ..>)
|
1294
|
-
```
|
1295
|
-
|
1296
|
-
Representable will not attempt to create a `Song` instance for you but use the provided from the document.
|
1297
|
-
|
1298
|
-
Note that this is now the [official option](#syncing-objects) `:parse_strategy`.
|
1299
|
-
|
1300
|
-
|
1301
|
-
### Rendering And Parsing Without Extend
|
1302
|
-
|
1303
|
-
Sometimes you wanna skip the preparation step when rendering and parsing, for instance, when the object already exposes a `#to_hash`/`#from_hash` method.
|
1304
|
-
|
1305
|
-
```ruby
|
1306
|
-
class ParsingSong
|
1307
|
-
def from_hash(hash, *args)
|
1308
|
-
# do whatever
|
1309
|
-
|
1310
|
-
self
|
1311
|
-
end
|
1312
|
-
|
1313
|
-
def to_hash(*args)
|
1314
|
-
{}
|
1315
|
-
end
|
1316
|
-
end
|
1317
|
-
```
|
1318
|
-
|
1319
|
-
This would work with a representer as the following.
|
1320
|
-
|
1321
|
-
```ruby
|
1322
|
-
property :song, :class => ParsingSong, prepare: lambda { |object| object }
|
1323
|
-
```
|
1324
|
-
|
1325
|
-
Instead of automatically extending/decorating the object, the `:prepare` lambda is run. It's up to you to prepare you object - or simply return it, as in the above example.
|
1326
|
-
|
1327
|
-
|
1328
|
-
### Skipping Rendering Or Parsing
|
1329
|
-
|
1330
|
-
You can skip to call to `#to_hash`/`#from_hash` on the prepared object by using `:representable`.
|
1331
|
-
|
1332
|
-
```ruby
|
1333
|
-
property :song, :representable => false
|
1334
|
-
```
|
1335
|
-
|
1336
|
-
This will run the entire serialization/deserialization _without_ calling the actual representing method on the object.
|
1337
|
-
|
1338
|
-
Extremely helpful if you wanna use representable as a data mapping tool with filtering, aliasing, etc., without the rendering and parsing part.
|
1339
|
-
|
1340
|
-
|
1341
|
-
### Returning Arbitrary Objects When Parsing
|
1342
|
-
|
1343
|
-
When representable parses the `song` attribute, it calls `ParsingSong#from_hash`. This method could return any object, which will then be assigned as the `song` property.
|
1344
|
-
|
1345
|
-
```ruby
|
1346
|
-
class ParsingSong
|
1347
|
-
def from_hash(hash, *args)
|
1348
|
-
[1,2,3,4]
|
1349
|
-
end
|
1350
|
-
end
|
1351
|
-
```
|
1352
|
-
|
1353
|
-
Album.extend(AlbumRepresenter).from_hash(..).song #=> [1,2,3,4]
|
1354
|
-
|
1355
|
-
This also works with `:extend` where the specified module overwrites the parsing method (e.g. `#from_hash`).
|
1356
|
-
|
1357
|
-
|
1358
|
-
### Decorator In Module
|
1359
|
-
|
1360
|
-
Inline representers defined in a module can be implemented as a decorator, thus wrapping the represented object without pollution.
|
1361
|
-
|
1362
|
-
```ruby
|
1363
|
-
property :song, use_decorator: true do
|
1364
|
-
property :title
|
1365
|
-
end
|
1366
|
-
```
|
1367
|
-
|
1368
|
-
This is an implementation detail most people shouldn't worry about.
|
1369
|
-
|
1370
|
-
|
1371
|
-
### Defaults
|
1372
|
-
|
1373
|
-
Declaring default options for `property`, `nested`, `collection` and `hash` is
|
1374
|
-
easy as:
|
1375
|
-
|
1376
|
-
```ruby
|
1377
|
-
defaults render_nil: true
|
1378
|
-
```
|
1379
|
-
|
1380
|
-
You can also pass a block for dynamic default options based on the property
|
1381
|
-
name or on the property options.
|
1382
|
-
|
1383
|
-
```ruby
|
1384
|
-
defaults render_nil: true do |name|
|
1385
|
-
{ as: name.to_s.camelize }
|
1386
|
-
end
|
1387
|
-
```
|
1388
|
-
|
1389
|
-
```ruby
|
1390
|
-
defaults render_nil: true do |name, options|
|
1391
|
-
{
|
1392
|
-
as: name.to_s.camelize,
|
1393
|
-
embedded: options.fetch(:hash, false)
|
1394
|
-
}
|
1395
|
-
end
|
1396
|
-
```
|
1397
|
-
|
1398
|
-
## Symbol Keys vs. String Keys
|
1399
|
-
|
1400
|
-
When parsing representable reads properties from hashes by using their string keys.
|
1401
|
-
|
1402
|
-
```ruby
|
1403
|
-
song.from_hash("title" => "Road To Never")
|
1404
|
-
```
|
1405
|
-
|
1406
|
-
To allow symbol keys also include the `AllowSymbols` module.
|
1407
|
-
|
1408
|
-
```ruby
|
1409
|
-
module SongRepresenter
|
1410
|
-
include Representable::Hash
|
1411
|
-
include Representable::Hash::AllowSymbols
|
1412
|
-
# ..
|
1413
|
-
end
|
1414
|
-
```
|
1415
|
-
|
1416
|
-
This will give you a behavior close to Rails' `HashWithIndifferentAccess` by stringifying the incoming hash internally.
|
1417
|
-
|
1418
|
-
## Object to Object Mapping
|
1419
|
-
|
1420
|
-
Since Representable 2.1.6 we also allow mapping objects to objects directly. This is extremely fast as the source object is queried for its properties and then values are directly written to the target object.
|
1421
|
-
|
1422
|
-
`````ruby
|
1423
|
-
require "representable/object"
|
1424
|
-
|
1425
|
-
module SongRepresenter
|
1426
|
-
include Representable::Object
|
1427
|
-
|
1428
|
-
property :title
|
1429
|
-
end
|
1430
|
-
```
|
1431
|
-
|
1432
|
-
As always, the source object needs to have readers for all properties and the target writers.
|
1433
|
-
|
1434
|
-
```ruby
|
1435
|
-
song_copy = OpenStruct.new.extend(SongRepresenter).from_object(song)
|
1436
|
-
song_copy.title = "Roxanne"
|
1437
|
-
```
|
1438
|
-
|
1439
|
-
All kind of transformation options can be used as well as nesting.
|
1440
|
-
|
1441
|
-
## Debugging
|
1442
|
-
|
1443
|
-
Representable is a generic mapper using recursions and things that might be hard to understand from the outside. That's why we got the `Debug` module which will give helpful output about what it's doing when parsing or rendering.
|
1444
|
-
|
1445
|
-
You can extend objects on the run to see what they're doing.
|
1446
|
-
|
1447
|
-
```ruby
|
1448
|
-
song.extend(SongRepresenter).extend(Representable::Debug).from_json("..")
|
1449
|
-
song.extend(SongRepresenter).extend(Representable::Debug).to_json
|
1450
|
-
```
|
1451
|
-
|
1452
|
-
`Debug` can also be included statically into your representer or decorator.
|
1453
|
-
|
1454
|
-
```ruby
|
1455
|
-
class SongRepresenter < Representable::Decorator
|
1456
|
-
include Representable::JSON
|
1457
|
-
include Representable::Debug
|
1458
|
-
|
1459
|
-
property :title
|
1460
|
-
end
|
1461
|
-
```
|
1462
|
-
|
1463
|
-
It's probably a good idea not to do this in production.
|
1464
|
-
|
1465
|
-
|
1466
168
|
## Copyright
|
1467
169
|
|
1468
|
-
Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his inspiring work.
|
170
|
+
Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his extremely inspiring work.
|
1469
171
|
|
1470
|
-
* Copyright (c) 2011-
|
172
|
+
* Copyright (c) 2011-2016 Nick Sutterer <apotonick@gmail.com>
|
1471
173
|
* ROXML is Copyright (c) 2004-2009 Ben Woosley, Zak Mandhro and Anders Engstrom.
|
1472
174
|
|
1473
175
|
Representable is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|