representable 1.4.2 → 1.5.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.
- data/CHANGES.textile +4 -0
- data/README.md +323 -229
- data/TODO +1 -1
- data/lib/representable/binding.rb +24 -19
- data/lib/representable/version.rb +1 -1
- data/test/representable_test.rb +9 -9
- metadata +2 -2
data/CHANGES.textile
CHANGED
@@ -1,3 +1,7 @@
|
|
1
|
+
h2. 1.5.0
|
2
|
+
|
3
|
+
* All lambdas now receive user options, too. Note that this might break your existing lambdas (especially with `:extend` or `:class`) raising an `ArgumentError: wrong number of arguments (2 for 1)`. Fix this by declaring your block params correctly, e.g. `lambda { |name, *|`. Internally, this happens by running all lambdas through the new `Binding#represented_exec_for`.
|
4
|
+
|
1
5
|
h2. 1.4.2
|
2
6
|
|
3
7
|
* Fix the processing of `:setter`, we called both the setter lambda and the setter method.
|
data/README.md
CHANGED
@@ -11,31 +11,35 @@ Representable is helpful for all kind of rendering and parsing workflows. Howeve
|
|
11
11
|
|
12
12
|
The representable gem is almost dependency-free. Almost.
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
```ruby
|
15
|
+
gem 'representable'
|
16
|
+
```
|
16
17
|
|
17
18
|
## Example
|
18
19
|
|
19
20
|
What if we're writing an API for music - songs, albums, bands.
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
song = Song.new(title: "Fallout", track: 1)
|
22
|
+
```ruby
|
23
|
+
class Song < OpenStruct
|
24
|
+
end
|
25
25
|
|
26
|
+
song = Song.new(title: "Fallout", track: 1)
|
27
|
+
```
|
26
28
|
|
27
29
|
## Defining Representations
|
28
30
|
|
29
31
|
Representations are defined using representer modules.
|
30
32
|
|
31
|
-
|
33
|
+
```ruby
|
34
|
+
require 'representable/json'
|
32
35
|
|
33
|
-
|
34
|
-
|
36
|
+
module SongRepresenter
|
37
|
+
include Representable::JSON
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
property :title
|
40
|
+
property :track
|
41
|
+
end
|
42
|
+
```
|
39
43
|
|
40
44
|
In the representer the #property method allows declaring represented attributes of the object. All the representer requires for rendering are readers on the represented object, e.g. `#title` and `#track`. When parsing, it will call setters - in our example, that'd be `#title=` and `#track=`.
|
41
45
|
|
@@ -44,17 +48,19 @@ In the representer the #property method allows declaring represented attributes
|
|
44
48
|
|
45
49
|
Mixing in the representer into the object adds a rendering method.
|
46
50
|
|
47
|
-
|
48
|
-
|
49
|
-
|
51
|
+
```ruby
|
52
|
+
song.extend(SongRepresenter).to_json
|
53
|
+
#=> {"title":"Fallout","track":1}
|
54
|
+
```
|
50
55
|
|
51
56
|
## Parsing
|
52
57
|
|
53
58
|
It also adds support for parsing.
|
54
59
|
|
55
|
-
|
56
|
-
|
57
|
-
|
60
|
+
```ruby
|
61
|
+
song = Song.new.extend(SongRepresenter).from_json(%{ {"title":"Roxanne"} })
|
62
|
+
#=> #<Song title="Roxanne", track=nil>
|
63
|
+
```
|
58
64
|
|
59
65
|
## Extend vs. Decorator
|
60
66
|
|
@@ -65,33 +71,39 @@ If you don't want representer modules to be mixed into your objects (using `#ext
|
|
65
71
|
|
66
72
|
If your property name doesn't match the name in the document, use the `:as` option.
|
67
73
|
|
68
|
-
|
69
|
-
|
74
|
+
```ruby
|
75
|
+
module SongRepresenter
|
76
|
+
include Representable::JSON
|
70
77
|
|
71
|
-
|
72
|
-
|
73
|
-
|
78
|
+
property :title, as: :name
|
79
|
+
property :track
|
80
|
+
end
|
74
81
|
|
75
|
-
|
82
|
+
song.to_json #=> {"name":"Fallout","track":1}
|
83
|
+
```
|
76
84
|
|
77
85
|
|
78
86
|
## Wrapping
|
79
87
|
|
80
88
|
Let the representer know if you want wrapping.
|
81
89
|
|
82
|
-
|
83
|
-
|
90
|
+
```ruby
|
91
|
+
module SongRepresenter
|
92
|
+
include Representable::JSON
|
84
93
|
|
85
|
-
|
94
|
+
self.representation_wrap= :hit
|
86
95
|
|
87
|
-
|
88
|
-
|
89
|
-
|
96
|
+
property :title
|
97
|
+
property :track
|
98
|
+
end
|
99
|
+
```
|
90
100
|
|
91
101
|
This will add a container for rendering and consuming.
|
92
102
|
|
93
|
-
|
94
|
-
|
103
|
+
```ruby
|
104
|
+
song.extend(SongRepresenter).to_json
|
105
|
+
#=> {"hit":{"title":"Fallout","track":1}}
|
106
|
+
```
|
95
107
|
|
96
108
|
Setting `self.representation_wrap = true` will advice representable to figure out the wrap itself by inspecting the represented object class.
|
97
109
|
|
@@ -100,21 +112,24 @@ Setting `self.representation_wrap = true` will advice representable to figure ou
|
|
100
112
|
|
101
113
|
Let's add a list of composers to the song representation.
|
102
114
|
|
103
|
-
|
104
|
-
|
115
|
+
```ruby
|
116
|
+
module SongRepresenter
|
117
|
+
include Representable::JSON
|
105
118
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
119
|
+
property :title
|
120
|
+
property :track
|
121
|
+
collection :composers
|
122
|
+
end
|
123
|
+
```
|
110
124
|
|
111
125
|
Surprisingly, `#collection` lets us define lists of objects to represent.
|
112
126
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
#=> {"title":"Fallout","composers":["Steward Copeland","Sting"]}
|
127
|
+
```ruby
|
128
|
+
Song.new(title: "Fallout", composers: ["Steward Copeland", "Sting"]).
|
129
|
+
extend(SongRepresenter).to_json
|
117
130
|
|
131
|
+
#=> {"title":"Fallout","composers":["Steward Copeland","Sting"]}
|
132
|
+
```
|
118
133
|
|
119
134
|
And again, this works both ways - in addition to the title it extracts the composers from the document, too.
|
120
135
|
|
@@ -123,111 +138,135 @@ And again, this works both ways - in addition to the title it extracts the compo
|
|
123
138
|
|
124
139
|
Representers can also manage compositions. Why not use an album that contains a list of songs?
|
125
140
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
album = Album.new(name: "The Police", songs: [song, Song.new(title: "Synchronicity")])
|
141
|
+
```ruby
|
142
|
+
class Album < OpenStruct
|
143
|
+
end
|
130
144
|
|
145
|
+
album = Album.new(name: "The Police", songs: [song, Song.new(title: "Synchronicity")])
|
146
|
+
```
|
131
147
|
|
132
148
|
Here comes the representer that defines the composition.
|
133
149
|
|
134
|
-
|
135
|
-
|
150
|
+
```ruby
|
151
|
+
module AlbumRepresenter
|
152
|
+
include Representable::JSON
|
136
153
|
|
137
|
-
|
138
|
-
|
139
|
-
|
154
|
+
property :name
|
155
|
+
collection :songs, extend: SongRepresenter, class: Song
|
156
|
+
end
|
157
|
+
```
|
140
158
|
|
141
159
|
Note that nesting works with both plain `#property` and `#collection`.
|
142
160
|
|
143
161
|
When rendering, the `:extend` module is used to extend the attribute(s) with the correct representer module.
|
144
162
|
|
145
|
-
|
146
|
-
|
163
|
+
```ruby
|
164
|
+
album.extend(AlbumRepresenter).to_json
|
165
|
+
#=> {"name":"The Police","songs":[{"title":"Fallout","composers":["Steward Copeland","Sting"]},{"title":"Synchronicity","composers":[]}]}
|
166
|
+
```
|
147
167
|
|
148
168
|
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.
|
149
169
|
|
150
|
-
|
151
|
-
|
170
|
+
```ruby
|
171
|
+
Album.new.extend(AlbumRepresenter).
|
172
|
+
from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]}})
|
152
173
|
|
153
|
-
|
174
|
+
#=> #<Album name="Offspring", songs=[#<Song title="Genocide">, #<Song title="Nitro", composers=["Offspring"]>]>
|
175
|
+
```
|
154
176
|
|
155
177
|
## Decorator vs. Extend
|
156
178
|
|
157
179
|
People who dislike `:extend` go use the `Decorator` strategy!
|
158
180
|
|
159
|
-
|
160
|
-
|
181
|
+
```ruby
|
182
|
+
class SongRepresentation < Representable::Decorator
|
183
|
+
include Representable::JSON
|
161
184
|
|
162
|
-
|
163
|
-
|
164
|
-
|
185
|
+
property :title
|
186
|
+
property :track
|
187
|
+
end
|
188
|
+
```
|
165
189
|
|
166
190
|
The `Decorator` constructor requires the represented object.
|
167
191
|
|
168
|
-
|
192
|
+
```ruby
|
193
|
+
SongRepresentation.new(song).to_json
|
194
|
+
```
|
169
195
|
|
170
196
|
This will leave the `song` instance untouched as the decorator just uses public accessors to represent the hit.
|
171
197
|
|
172
198
|
In compositions you need to specify the decorators for the nested items using the `:decorator` option where you'd normally use `:extend`.
|
173
199
|
|
174
|
-
|
175
|
-
|
200
|
+
```ruby
|
201
|
+
class AlbumRepresentation < Representable::Decorator
|
202
|
+
include Representable::JSON
|
176
203
|
|
177
|
-
|
178
|
-
|
204
|
+
collection :songs, :class => Song, :decorator => SongRepresentation
|
205
|
+
end
|
206
|
+
```
|
179
207
|
|
180
208
|
## XML Support
|
181
209
|
|
182
210
|
While representable does a great job with JSON, it also features support for XML, YAML and pure ruby hashes.
|
183
211
|
|
184
|
-
|
212
|
+
```ruby
|
213
|
+
require 'representable/xml'
|
185
214
|
|
186
|
-
|
187
|
-
|
215
|
+
module SongRepresenter
|
216
|
+
include Representable::XML
|
188
217
|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
218
|
+
property :title
|
219
|
+
property :track
|
220
|
+
collection :composers
|
221
|
+
end
|
222
|
+
```
|
193
223
|
|
194
224
|
For XML we just include the `Representable::XML` module.
|
195
225
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
226
|
+
```xml
|
227
|
+
Song.new(title: "Fallout", composers: ["Steward Copeland", "Sting"]).
|
228
|
+
extend(SongRepresenter).to_xml #=>
|
229
|
+
<song>
|
230
|
+
<title>Fallout</title>
|
231
|
+
<composers>Steward Copeland</composers>
|
232
|
+
<composers>Sting</composers>
|
233
|
+
</song>
|
234
|
+
```
|
204
235
|
|
205
236
|
## Passing Options
|
206
237
|
|
207
238
|
You're free to pass an options hash into the rendering or parsing.
|
208
239
|
|
209
|
-
|
240
|
+
```ruby
|
241
|
+
song.to_json(:append => "SOLD OUT!")
|
242
|
+
```
|
210
243
|
|
211
244
|
If you want to append the "SOLD OUT!" to the song's `title` when rendering, use the `:getter` option.
|
212
245
|
|
213
|
-
|
214
|
-
|
246
|
+
```ruby
|
247
|
+
SongRepresenter
|
248
|
+
include Representable::JSON
|
215
249
|
|
216
|
-
|
217
|
-
|
250
|
+
property :title, :getter => lambda { |args| title + args[:append] }
|
251
|
+
end
|
252
|
+
```
|
218
253
|
|
219
254
|
Note that the block is executed in the represented model context which allows using accessors and instance variables.
|
220
255
|
|
221
256
|
|
222
257
|
The same works for parsing using the `:setter` method.
|
223
258
|
|
224
|
-
|
259
|
+
```ruby
|
260
|
+
property :title, :setter => lambda { |val, args| self.title= val + args[:append] }
|
261
|
+
```
|
225
262
|
|
226
263
|
Here, the block retrieves two arguments: the parsed value and your user options.
|
227
264
|
|
228
265
|
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.
|
229
266
|
|
230
|
-
|
267
|
+
```ruby
|
268
|
+
property :title, :getter => lambda { |*| @name }
|
269
|
+
```
|
231
270
|
|
232
271
|
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.
|
233
272
|
|
@@ -235,48 +274,55 @@ This hash will also be available in the `:if` block, documented [here](https://g
|
|
235
274
|
|
236
275
|
Sometimes it's useful to override accessors to customize output or parsing.
|
237
276
|
|
238
|
-
|
239
|
-
|
277
|
+
```ruby
|
278
|
+
module AlbumRepresenter
|
279
|
+
include Representable::JSON
|
240
280
|
|
241
|
-
|
242
|
-
|
281
|
+
property :name
|
282
|
+
collection :songs
|
243
283
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
284
|
+
def name
|
285
|
+
super.upcase
|
286
|
+
end
|
287
|
+
end
|
248
288
|
|
249
|
-
|
250
|
-
|
289
|
+
Album.new(:name => "The Police").
|
290
|
+
extend(AlbumRepresenter).to_json
|
251
291
|
|
252
|
-
|
292
|
+
#=> {"name":"THE POLICE","songs":[]}
|
293
|
+
```
|
253
294
|
|
254
295
|
Note how the representer allows calling `super` in order to access the original attribute method of the represented object.
|
255
296
|
|
256
297
|
To change the parsing process override the setter.
|
257
298
|
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
299
|
+
```ruby
|
300
|
+
def name=(value)
|
301
|
+
super(value.downcase)
|
302
|
+
end
|
303
|
+
```
|
262
304
|
|
263
305
|
## Inheritance
|
264
306
|
|
265
307
|
To reuse existing representers you can inherit from those modules.
|
266
308
|
|
267
|
-
|
268
|
-
|
269
|
-
|
309
|
+
```ruby
|
310
|
+
module CoverSongRepresenter
|
311
|
+
include Representable::JSON
|
312
|
+
include SongRepresenter
|
270
313
|
|
271
|
-
|
272
|
-
|
314
|
+
property :copyright
|
315
|
+
end
|
316
|
+
```
|
273
317
|
|
274
318
|
Inheritance works by `include`ing already defined representers.
|
275
319
|
|
276
|
-
|
277
|
-
|
320
|
+
```ruby
|
321
|
+
Song.new(:title => "Truth Hits Everybody", :copyright => "The Police").
|
322
|
+
extend(CoverSongRepresenter).to_json
|
278
323
|
|
279
|
-
|
324
|
+
#=> {"title":"Truth Hits Everybody","copyright":"The Police"}
|
325
|
+
```
|
280
326
|
|
281
327
|
|
282
328
|
## Polymorphic Extend
|
@@ -285,25 +331,30 @@ Sometimes heterogenous collections of objects from different classes must be rep
|
|
285
331
|
|
286
332
|
Given we not only have songs, but also cover songs.
|
287
333
|
|
288
|
-
|
289
|
-
|
334
|
+
```ruby
|
335
|
+
class CoverSong < Song
|
336
|
+
end
|
337
|
+
```
|
290
338
|
|
291
339
|
And a non-homogenous collection of songs.
|
292
340
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
album = Album.new(name: "Incognito", songs: songs)
|
341
|
+
```ruby
|
342
|
+
songs = [ Song.new(title: "Weirdo", track: 5),
|
343
|
+
CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")]
|
297
344
|
|
345
|
+
album = Album.new(name: "Incognito", songs: songs)
|
346
|
+
```
|
298
347
|
|
299
348
|
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!
|
300
349
|
|
301
|
-
|
302
|
-
|
350
|
+
```ruby
|
351
|
+
module AlbumRepresenter
|
352
|
+
include Representable::JSON
|
303
353
|
|
304
|
-
|
305
|
-
|
306
|
-
|
354
|
+
property :name
|
355
|
+
collection :songs, :extend => lambda { |song, *| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }
|
356
|
+
end
|
357
|
+
```
|
307
358
|
|
308
359
|
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.
|
309
360
|
|
@@ -312,87 +363,99 @@ Note that the lambda block is evaluated in the represented object context which
|
|
312
363
|
|
313
364
|
Rendering heterogenous collections usually implies that you also need to parse those. Luckily, `:class` also accepts a lambda.
|
314
365
|
|
315
|
-
|
316
|
-
|
366
|
+
```ruby
|
367
|
+
module AlbumRepresenter
|
368
|
+
include Representable::JSON
|
317
369
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
370
|
+
property :name
|
371
|
+
collection :songs,
|
372
|
+
:extend => ...,
|
373
|
+
:class => lambda { |hsh, *| hsh.has_key?("copyright") ? CoverSong : Song }
|
374
|
+
end
|
375
|
+
```
|
323
376
|
|
324
377
|
The block for `:class` receives the currently parsed fragment. Here, this might be somthing like `{"title"=>"Weirdo", "track"=>5}`.
|
325
378
|
|
326
379
|
If this is not enough, you may override the entire object creation process using `:instance`.
|
327
380
|
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
property :name
|
332
|
-
collection :songs,
|
333
|
-
:extend => ...,
|
334
|
-
:instance => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) }
|
335
|
-
end
|
381
|
+
```ruby
|
382
|
+
module AlbumRepresenter
|
383
|
+
include Representable::JSON
|
336
384
|
|
385
|
+
property :name
|
386
|
+
collection :songs,
|
387
|
+
:extend => ...,
|
388
|
+
:instance => lambda { |hsh, *| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) }
|
389
|
+
end
|
390
|
+
```
|
337
391
|
|
338
392
|
## Hashes
|
339
393
|
|
340
394
|
As an addition to single properties and collections representable also offers to represent hash attributes.
|
341
395
|
|
342
|
-
|
343
|
-
|
396
|
+
```ruby
|
397
|
+
module SongRepresenter
|
398
|
+
include Representable::JSON
|
344
399
|
|
345
|
-
|
346
|
-
|
347
|
-
|
400
|
+
property :title
|
401
|
+
hash :ratings
|
402
|
+
end
|
348
403
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
#=> {"title":"Bliss","ratings":{"Rolling Stone":4.9,"FryZine":4.5}}
|
404
|
+
Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).
|
405
|
+
extend(SongRepresenter).to_json
|
353
406
|
|
407
|
+
#=> {"title":"Bliss","ratings":{"Rolling Stone":4.9,"FryZine":4.5}}
|
408
|
+
```
|
354
409
|
|
355
410
|
## Lonely Hashes
|
356
411
|
|
357
412
|
Need to represent a bare hash without any container? Use the `JSON::Hash` representer (or XML::Hash).
|
358
413
|
|
359
|
-
|
414
|
+
```ruby
|
415
|
+
require 'representable/json/hash'
|
360
416
|
|
361
|
-
|
362
|
-
|
363
|
-
|
417
|
+
module FavoriteSongsRepresenter
|
418
|
+
include Representable::JSON::Hash
|
419
|
+
end
|
364
420
|
|
365
|
-
|
366
|
-
|
421
|
+
{"Nick" => "Hyper Music", "El" => "Blown In The Wind"}.extend(FavoriteSongsRepresenter).to_json
|
422
|
+
#=> {"Nick":"Hyper Music","El":"Blown In The Wind"}
|
423
|
+
```
|
367
424
|
|
368
425
|
Works both ways. The values are configurable and might be self-representing objects in turn. Tell the `Hash` by using `#values`.
|
369
426
|
|
370
|
-
|
371
|
-
|
427
|
+
```ruby
|
428
|
+
module FavoriteSongsRepresenter
|
429
|
+
include Representable::JSON::Hash
|
372
430
|
|
373
|
-
|
374
|
-
|
431
|
+
values extend: SongRepresenter, class: Song
|
432
|
+
end
|
375
433
|
|
376
|
-
|
434
|
+
{"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json
|
435
|
+
```
|
377
436
|
|
378
|
-
|
437
|
+
In XML, if you want to store hash attributes in tag attributes instead of dedicated nodes, use `XML::AttributeHash`.
|
379
438
|
|
380
439
|
## Lonely Collections
|
381
440
|
|
382
441
|
Same goes with arrays.
|
383
442
|
|
384
|
-
|
443
|
+
```ruby
|
444
|
+
require 'representable/json/collection'
|
385
445
|
|
386
|
-
|
387
|
-
|
446
|
+
module SongsRepresenter
|
447
|
+
include Representable::JSON::Collection
|
388
448
|
|
389
|
-
|
390
|
-
|
449
|
+
items extend: SongRepresenter, class: Song
|
450
|
+
end
|
451
|
+
```
|
391
452
|
|
392
453
|
The `#items` method lets you configure the contained entity representing here.
|
393
454
|
|
394
|
-
|
395
|
-
|
455
|
+
```ruby
|
456
|
+
[Song.new(title: "Hyper Music"), Song.new(title: "Screenager")].extend(SongsRepresenter).to_json
|
457
|
+
#=> [{"title":"Hyper Music"},{"title":"Screenager"}]
|
458
|
+
```
|
396
459
|
|
397
460
|
Note that this also works for XML.
|
398
461
|
|
@@ -401,22 +464,25 @@ Note that this also works for XML.
|
|
401
464
|
|
402
465
|
Representable also comes with a YAML representer.
|
403
466
|
|
404
|
-
|
405
|
-
|
467
|
+
```ruby
|
468
|
+
module SongRepresenter
|
469
|
+
include Representable::YAML
|
406
470
|
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
471
|
+
property :title
|
472
|
+
property :track
|
473
|
+
collection :composers, :style => :flow
|
474
|
+
end
|
475
|
+
```
|
411
476
|
|
412
477
|
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!
|
413
478
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
479
|
+
```ruby
|
480
|
+
song.extend(SongRepresenter).to_yaml
|
481
|
+
#=>
|
482
|
+
---
|
483
|
+
title: Fallout
|
484
|
+
composers: [Steward Copeland, Sting]
|
485
|
+
```
|
420
486
|
|
421
487
|
## More on XML
|
422
488
|
|
@@ -424,15 +490,17 @@ A nice feature is that `#collection` also accepts a `:style` option which helps
|
|
424
490
|
|
425
491
|
You can also map properties to tag attributes in representable.
|
426
492
|
|
427
|
-
|
428
|
-
|
493
|
+
```ruby
|
494
|
+
module SongRepresenter
|
495
|
+
include Representable::XML
|
429
496
|
|
430
|
-
|
431
|
-
|
432
|
-
|
497
|
+
property :title, attribute: true
|
498
|
+
property :track, attribute: true
|
499
|
+
end
|
433
500
|
|
434
|
-
|
435
|
-
|
501
|
+
Song.new(title: "American Idle").to_xml
|
502
|
+
#=> <song title="American Idle" />
|
503
|
+
```
|
436
504
|
|
437
505
|
Naturally, this works for both ways.
|
438
506
|
|
@@ -440,33 +508,38 @@ Naturally, this works for both ways.
|
|
440
508
|
|
441
509
|
It is sometimes unavoidable to wrap tag lists in a container tag.
|
442
510
|
|
443
|
-
|
444
|
-
|
511
|
+
```ruby
|
512
|
+
module AlbumRepresenter
|
513
|
+
include Representable::XML
|
445
514
|
|
446
|
-
|
447
|
-
|
515
|
+
collection :songs, :as => :song, :wrap => :songs
|
516
|
+
end
|
517
|
+
```
|
448
518
|
|
449
519
|
Note that `:wrap` defines the container tag name.
|
450
520
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
</
|
458
|
-
|
459
|
-
|
521
|
+
```xml
|
522
|
+
Album.new.to_xml #=>
|
523
|
+
<album>
|
524
|
+
<songs>
|
525
|
+
<song>Laundry Basket</song>
|
526
|
+
<song>Two Kevins</song>
|
527
|
+
<song>Wright and Rong</song>
|
528
|
+
</songs>
|
529
|
+
</album>
|
530
|
+
```
|
460
531
|
|
461
532
|
## Avoiding Modules
|
462
533
|
|
463
534
|
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.
|
464
535
|
|
465
|
-
|
466
|
-
|
536
|
+
```ruby
|
537
|
+
class Song < OpenStruct
|
538
|
+
include Representable::JSON
|
467
539
|
|
468
|
-
|
469
|
-
|
540
|
+
property :name
|
541
|
+
end
|
542
|
+
```
|
470
543
|
|
471
544
|
I do not recommend this approach as it bloats your domain classes with representation logic that is barely needed elsewhere.
|
472
545
|
|
@@ -480,19 +553,25 @@ Here's a quick overview about other available options for `#property` and its br
|
|
480
553
|
|
481
554
|
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.
|
482
555
|
|
483
|
-
|
556
|
+
```ruby
|
557
|
+
property :title, :writer => lambda { |doc, args| doc["title"] = title || original_title }
|
558
|
+
```
|
484
559
|
|
485
560
|
When using the `:writer` option it is up to you to add fragments to the `doc` - representable won't add anything for this property.
|
486
561
|
|
487
562
|
The same works for parsing using `:reader`.
|
488
563
|
|
489
|
-
|
564
|
+
```ruby
|
565
|
+
property :title, :reader => lambda { |doc, args| self.title = doc["title"] || doc["name"] }
|
566
|
+
```
|
490
567
|
|
491
568
|
### Read/Write Restrictions
|
492
569
|
|
493
570
|
Using the `:readable` and `:writeable` options access to properties can be restricted.
|
494
571
|
|
495
|
-
|
572
|
+
```ruby
|
573
|
+
property :title, :readable => false
|
574
|
+
```
|
496
575
|
|
497
576
|
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.
|
498
577
|
|
@@ -501,31 +580,37 @@ This will leave out the `title` property in the rendered document. Vice-versa, `
|
|
501
580
|
|
502
581
|
Representable also allows you to skip and include properties using the `:exclude` and `:include` options passed directly to the respective method.
|
503
582
|
|
504
|
-
|
505
|
-
|
506
|
-
|
583
|
+
```ruby
|
584
|
+
song.to_json(:include => :title)
|
585
|
+
#=> {"title":"Roxanne"}
|
586
|
+
```
|
507
587
|
|
508
588
|
### Conditions
|
509
589
|
|
510
590
|
You can also define conditions on properties using `:if`, making them being considered only when the block returns a true value.
|
511
591
|
|
512
|
-
|
513
|
-
|
592
|
+
```ruby
|
593
|
+
module SongRepresenter
|
594
|
+
include Representable::JSON
|
514
595
|
|
515
|
-
|
516
|
-
|
517
|
-
|
596
|
+
property :title
|
597
|
+
property :track, if: lambda { track > 0 }
|
598
|
+
end
|
599
|
+
```
|
518
600
|
|
519
601
|
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.
|
520
602
|
|
521
603
|
As always, the block retrieves your options. Given this render call
|
522
604
|
|
523
|
-
|
605
|
+
```ruby
|
606
|
+
song.to_json(minimum_track: 2)
|
607
|
+
```
|
524
608
|
|
525
609
|
your `:if` may process the options.
|
526
610
|
|
527
|
-
|
528
|
-
|
611
|
+
```ruby
|
612
|
+
property :track, if: lambda { |opts| track > opts[:minimum_track] }
|
613
|
+
```
|
529
614
|
|
530
615
|
### False and Nil Values
|
531
616
|
|
@@ -533,7 +618,9 @@ Since representable-1.2 `false` values _are_ considered when parsing and renderi
|
|
533
618
|
|
534
619
|
If you want `nil` values to be included when rendering, use the `:render_nil` option.
|
535
620
|
|
536
|
-
|
621
|
+
```ruby
|
622
|
+
property :track, render_nil: true
|
623
|
+
```
|
537
624
|
|
538
625
|
## Coercion
|
539
626
|
|
@@ -541,18 +628,21 @@ If you fancy coercion when parsing a document you can use the Coercion module wh
|
|
541
628
|
|
542
629
|
Include virtus in your Gemfile, first. Be sure to include virtus 0.5.0 or greater.
|
543
630
|
|
544
|
-
|
631
|
+
```ruby
|
632
|
+
gem 'virtus', ">= 0.5.0"
|
633
|
+
```
|
545
634
|
|
546
635
|
Use the `:type` option to specify the conversion target. Note that `:default` still works.
|
547
636
|
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
property :title
|
553
|
-
property :recorded_at, :type => DateTime, :default => "May 12th, 2012"
|
554
|
-
end
|
637
|
+
```ruby
|
638
|
+
module SongRepresenter
|
639
|
+
include Representable::JSON
|
640
|
+
include Representable::Coercion
|
555
641
|
|
642
|
+
property :title
|
643
|
+
property :recorded_at, :type => DateTime, :default => "May 12th, 2012"
|
644
|
+
end
|
645
|
+
```
|
556
646
|
|
557
647
|
## Undocumented Features
|
558
648
|
|
@@ -561,14 +651,18 @@ Use the `:type` option to specify the conversion target. Note that `:default` st
|
|
561
651
|
* If you need a special binding for a property you're free to create it using the `:binding` option.
|
562
652
|
|
563
653
|
<!-- here comes some code -->
|
564
|
-
|
654
|
+
```ruby
|
655
|
+
property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
|
656
|
+
```
|
565
657
|
|
566
658
|
* Lambdas are usually executed in the represented object's context. If your writing a `Decorator` representer and you need to execute lambdas in its context use the `:representer_exec` option.
|
567
659
|
|
568
660
|
<!-- and some more in a beautiful cuddle -->
|
569
|
-
|
570
|
-
|
571
|
-
|
661
|
+
```ruby
|
662
|
+
class SongRepresenter < Representable::Decorator
|
663
|
+
property :title, :representer_exec => true, :getter => lambda {..}
|
664
|
+
end
|
665
|
+
```
|
572
666
|
|
573
667
|
You can still access the represented object in the lambda using `represented`. In a module representer this option is ignored.
|
574
668
|
|
@@ -579,4 +673,4 @@ Representable started as a heavily simplified fork of the ROXML gem. Big thanks
|
|
579
673
|
* Copyright (c) 2011-2013 Nick Sutterer <apotonick@gmail.com>
|
580
674
|
* ROXML is Copyright (c) 2004-2009 Ben Woosley, Zak Mandhro and Anders Engstrom.
|
581
675
|
|
582
|
-
Representable is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
676
|
+
Representable is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|
data/TODO
CHANGED
@@ -36,17 +36,17 @@ module Representable
|
|
36
36
|
|
37
37
|
# Retrieve value and write fragment to the doc.
|
38
38
|
def compile_fragment(doc)
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
represented_exec_for(:writer, doc) do
|
40
|
+
write_fragment(doc, get)
|
41
|
+
end
|
42
42
|
end
|
43
43
|
|
44
44
|
# Parse value from doc and update the model property.
|
45
45
|
def uncompile_fragment(doc)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
represented_exec_for(:reader, doc) do
|
47
|
+
read_fragment(doc) do |value|
|
48
|
+
set(value)
|
49
|
+
end
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -77,20 +77,31 @@ module Representable
|
|
77
77
|
end
|
78
78
|
|
79
79
|
def get
|
80
|
-
|
81
|
-
|
80
|
+
represented_exec_for(:getter) do
|
81
|
+
represented.send(getter)
|
82
|
+
end
|
82
83
|
end
|
83
84
|
|
84
85
|
def set(value)
|
85
|
-
|
86
|
-
|
86
|
+
represented_exec_for(:setter, value) do
|
87
|
+
represented.send(setter, value)
|
88
|
+
end
|
87
89
|
end
|
88
90
|
|
89
91
|
private
|
90
92
|
# Execute the block for +option_name+ on the represented object.
|
93
|
+
# Executes passed block when there's no lambda for option.
|
91
94
|
def represented_exec_for(option_name, *args)
|
92
|
-
return unless options[option_name]
|
93
|
-
|
95
|
+
return yield unless options[option_name]
|
96
|
+
call_proc_for(options[option_name], *args)
|
97
|
+
end
|
98
|
+
|
99
|
+
# All lambdas are executed on lambda_context which is either represented or the decorator instance.
|
100
|
+
def call_proc_for(proc, *args)
|
101
|
+
return proc unless proc.is_a?(Proc)
|
102
|
+
# TODO: call method when proc is sympbol.
|
103
|
+
args << user_options # DISCUSS: we assume user_options is a Hash!
|
104
|
+
lambda_context.instance_exec(*args, &proc)
|
94
105
|
end
|
95
106
|
|
96
107
|
|
@@ -117,12 +128,6 @@ module Representable
|
|
117
128
|
def representer_module_for(object, *args)
|
118
129
|
call_proc_for(representer_module, object) # TODO: how to pass additional data to the computing block?`
|
119
130
|
end
|
120
|
-
|
121
|
-
def call_proc_for(proc, *args)
|
122
|
-
return proc unless proc.is_a?(Proc)
|
123
|
-
# DISCUSS: use represented_exec_for here?
|
124
|
-
@represented.instance_exec(*args, &proc)
|
125
|
-
end
|
126
131
|
end
|
127
132
|
|
128
133
|
# Overrides #serialize/#deserialize to call #to_*/from_*.
|
data/test/representable_test.rb
CHANGED
@@ -558,7 +558,7 @@ class RepresentableTest < MiniTest::Spec
|
|
558
558
|
|
559
559
|
describe "lambda blocks" do
|
560
560
|
representer! do
|
561
|
-
property :name, :extend => lambda { |name
|
561
|
+
property :name, :extend => lambda { |name, *| compute_representer(name) }
|
562
562
|
end
|
563
563
|
|
564
564
|
it "executes lambda in represented instance context" do
|
@@ -575,7 +575,7 @@ class RepresentableTest < MiniTest::Spec
|
|
575
575
|
obj = String.new("Fate")
|
576
576
|
mod = Module.new { include Representable; def from_hash(*); self; end }
|
577
577
|
representer! do
|
578
|
-
property :name, :extend => mod, :instance => lambda {
|
578
|
+
property :name, :extend => mod, :instance => lambda { |*| obj }
|
579
579
|
end
|
580
580
|
|
581
581
|
it "uses object from :instance but still extends it" do
|
@@ -587,7 +587,7 @@ class RepresentableTest < MiniTest::Spec
|
|
587
587
|
|
588
588
|
describe "property with :extend" do
|
589
589
|
representer! do
|
590
|
-
property :name, :extend => lambda { |name
|
590
|
+
property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
|
591
591
|
end
|
592
592
|
|
593
593
|
it "uses lambda when rendering" do
|
@@ -602,8 +602,8 @@ class RepresentableTest < MiniTest::Spec
|
|
602
602
|
|
603
603
|
describe "with :class lambda" do
|
604
604
|
representer! do
|
605
|
-
property :name, :extend => lambda { |name
|
606
|
-
:class => lambda { |fragment
|
605
|
+
property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
|
606
|
+
:class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
|
607
607
|
end
|
608
608
|
|
609
609
|
it "creates instance from :class lambda when parsing" do
|
@@ -618,7 +618,7 @@ class RepresentableTest < MiniTest::Spec
|
|
618
618
|
|
619
619
|
describe "when :class lambda returns nil" do
|
620
620
|
representer! do
|
621
|
-
property :name, :extend => lambda {
|
621
|
+
property :name, :extend => lambda { |*| Module.new { include Representable; def from_hash(data, *args); data; end } },
|
622
622
|
:class => nil
|
623
623
|
end
|
624
624
|
|
@@ -633,7 +633,7 @@ class RepresentableTest < MiniTest::Spec
|
|
633
633
|
|
634
634
|
describe "collection with :extend" do
|
635
635
|
representer! do
|
636
|
-
collection :songs, :extend => lambda { |name
|
636
|
+
collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
|
637
637
|
end
|
638
638
|
|
639
639
|
it "uses lambda for each item when rendering" do
|
@@ -647,8 +647,8 @@ class RepresentableTest < MiniTest::Spec
|
|
647
647
|
|
648
648
|
describe "with :class lambda" do
|
649
649
|
representer! do
|
650
|
-
collection :songs, :extend => lambda { |name
|
651
|
-
:class => lambda { |fragment
|
650
|
+
collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
|
651
|
+
:class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
|
652
652
|
end
|
653
653
|
|
654
654
|
it "creates instance from :class lambda for each item when parsing" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: representable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|