representable 1.4.2 → 1.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- gem 'representable'
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
- class Song < OpenStruct
22
- end
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
- require 'representable/json'
33
+ ```ruby
34
+ require 'representable/json'
32
35
 
33
- module SongRepresenter
34
- include Representable::JSON
36
+ module SongRepresenter
37
+ include Representable::JSON
35
38
 
36
- property :title
37
- property :track
38
- end
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
- song.extend(SongRepresenter).to_json
48
- #=> {"title":"Fallout","track":1}
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
- song = Song.new.extend(SongRepresenter).from_json(%{ {"title":"Roxanne"} })
56
- #=> #<Song title="Roxanne", track=nil>
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
- module SongRepresenter
69
- include Representable::JSON
74
+ ```ruby
75
+ module SongRepresenter
76
+ include Representable::JSON
70
77
 
71
- property :title, as: :name
72
- property :track
73
- end
78
+ property :title, as: :name
79
+ property :track
80
+ end
74
81
 
75
- song.to_json #=> {"name":"Fallout","track":1}
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
- module SongRepresenter
83
- include Representable::JSON
90
+ ```ruby
91
+ module SongRepresenter
92
+ include Representable::JSON
84
93
 
85
- self.representation_wrap= :hit
94
+ self.representation_wrap= :hit
86
95
 
87
- property :title
88
- property :track
89
- end
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
- song.extend(SongRepresenter).to_json
94
- #=> {"hit":{"title":"Fallout","track":1}}
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
- module SongRepresenter
104
- include Representable::JSON
115
+ ```ruby
116
+ module SongRepresenter
117
+ include Representable::JSON
105
118
 
106
- property :title
107
- property :track
108
- collection :composers
109
- end
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
- Song.new(title: "Fallout", composers: ["Steward Copeland", "Sting"]).
114
- extend(SongRepresenter).to_json
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
- class Album < OpenStruct
127
- end
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
- module AlbumRepresenter
135
- include Representable::JSON
150
+ ```ruby
151
+ module AlbumRepresenter
152
+ include Representable::JSON
136
153
 
137
- property :name
138
- collection :songs, extend: SongRepresenter, class: Song
139
- end
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
- album.extend(AlbumRepresenter).to_json
146
- #=> {"name":"The Police","songs":[{"title":"Fallout","composers":["Steward Copeland","Sting"]},{"title":"Synchronicity","composers":[]}]}
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
- Album.new.extend(AlbumRepresenter).
151
- from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]}})
170
+ ```ruby
171
+ Album.new.extend(AlbumRepresenter).
172
+ from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]}})
152
173
 
153
- #=> #<Album name="Offspring", songs=[#<Song title="Genocide">, #<Song title="Nitro", composers=["Offspring"]>]>
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
- class SongRepresentation < Representable::Decorator
160
- include Representable::JSON
181
+ ```ruby
182
+ class SongRepresentation < Representable::Decorator
183
+ include Representable::JSON
161
184
 
162
- property :title
163
- property :track
164
- end
185
+ property :title
186
+ property :track
187
+ end
188
+ ```
165
189
 
166
190
  The `Decorator` constructor requires the represented object.
167
191
 
168
- SongRepresentation.new(song).to_json
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
- class AlbumRepresentation < Representable::Decorator
175
- include Representable::JSON
200
+ ```ruby
201
+ class AlbumRepresentation < Representable::Decorator
202
+ include Representable::JSON
176
203
 
177
- collection :songs, :class => Song, :decorator => SongRepresentation
178
- end
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
- require 'representable/xml'
212
+ ```ruby
213
+ require 'representable/xml'
185
214
 
186
- module SongRepresenter
187
- include Representable::XML
215
+ module SongRepresenter
216
+ include Representable::XML
188
217
 
189
- property :title
190
- property :track
191
- collection :composers
192
- end
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
- Song.new(title: "Fallout", composers: ["Steward Copeland", "Sting"]).
197
- extend(SongRepresenter).to_xml
198
-
199
- #=> <song>
200
- <title>Fallout</title>
201
- <composers>Steward Copeland</composers>
202
- <composers>Sting</composers>
203
- </song>
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
- song.to_json(:append => "SOLD OUT!")
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
- SongRepresenter
214
- include Representable::JSON
246
+ ```ruby
247
+ SongRepresenter
248
+ include Representable::JSON
215
249
 
216
- property :title, :getter => lambda { |args| title + args[:append] }
217
- end
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
- property :title, :setter => lambda { |val, args| self.title= val + args[:append] }
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
- property :title, :getter => lambda { |*| @name }
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
- module AlbumRepresenter
239
- include Representable::JSON
277
+ ```ruby
278
+ module AlbumRepresenter
279
+ include Representable::JSON
240
280
 
241
- property :name
242
- collection :songs
281
+ property :name
282
+ collection :songs
243
283
 
244
- def name
245
- super.upcase
246
- end
247
- end
284
+ def name
285
+ super.upcase
286
+ end
287
+ end
248
288
 
249
- Album.new(:name => "The Police").
250
- extend(AlbumRepresenter).to_json
289
+ Album.new(:name => "The Police").
290
+ extend(AlbumRepresenter).to_json
251
291
 
252
- #=> {"name":"THE POLICE","songs":[]}
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
- def name=(value)
259
- super(value.downcase)
260
- end
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
- module CoverSongRepresenter
268
- include Representable::JSON
269
- include SongRepresenter
309
+ ```ruby
310
+ module CoverSongRepresenter
311
+ include Representable::JSON
312
+ include SongRepresenter
270
313
 
271
- property :copyright
272
- end
314
+ property :copyright
315
+ end
316
+ ```
273
317
 
274
318
  Inheritance works by `include`ing already defined representers.
275
319
 
276
- Song.new(:title => "Truth Hits Everybody", :copyright => "The Police").
277
- extend(CoverSongRepresenter).to_json
320
+ ```ruby
321
+ Song.new(:title => "Truth Hits Everybody", :copyright => "The Police").
322
+ extend(CoverSongRepresenter).to_json
278
323
 
279
- #=> {"title":"Truth Hits Everybody","copyright":"The Police"}
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
- class CoverSong < Song
289
- end
334
+ ```ruby
335
+ class CoverSong < Song
336
+ end
337
+ ```
290
338
 
291
339
  And a non-homogenous collection of songs.
292
340
 
293
- songs = [ Song.new(title: "Weirdo", track: 5),
294
- CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")]
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
- module AlbumRepresenter
302
- include Representable::JSON
350
+ ```ruby
351
+ module AlbumRepresenter
352
+ include Representable::JSON
303
353
 
304
- property :name
305
- collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }
306
- end
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
- module AlbumRepresenter
316
- include Representable::JSON
366
+ ```ruby
367
+ module AlbumRepresenter
368
+ include Representable::JSON
317
369
 
318
- property :name
319
- collection :songs,
320
- :extend => ...,
321
- :class => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong : Song }
322
- end
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
- module AlbumRepresenter
329
- include Representable::JSON
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
- module SongRepresenter
343
- include Representable::JSON
396
+ ```ruby
397
+ module SongRepresenter
398
+ include Representable::JSON
344
399
 
345
- property :title
346
- hash :ratings
347
- end
400
+ property :title
401
+ hash :ratings
402
+ end
348
403
 
349
- Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).
350
- extend(SongRepresenter).to_json
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
- require 'representable/json/hash'
414
+ ```ruby
415
+ require 'representable/json/hash'
360
416
 
361
- module FavoriteSongsRepresenter
362
- include Representable::JSON::Hash
363
- end
417
+ module FavoriteSongsRepresenter
418
+ include Representable::JSON::Hash
419
+ end
364
420
 
365
- {"Nick" => "Hyper Music", "El" => "Blown In The Wind"}.extend(FavoriteSongsRepresenter).to_json
366
- #=> {"Nick":"Hyper Music","El":"Blown In The Wind"}
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
- module FavoriteSongsRepresenter
371
- include Representable::JSON::Hash
427
+ ```ruby
428
+ module FavoriteSongsRepresenter
429
+ include Representable::JSON::Hash
372
430
 
373
- values extend: SongRepresenter, class: Song
374
- end
431
+ values extend: SongRepresenter, class: Song
432
+ end
375
433
 
376
- {"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json
434
+ {"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json
435
+ ```
377
436
 
378
- In XML, if you want to store hash attributes in tag attributes instead of dedicated nodes, use `XML::AttributeHash`.
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
- require 'representable/json/collection'
443
+ ```ruby
444
+ require 'representable/json/collection'
385
445
 
386
- module SongsRepresenter
387
- include Representable::JSON::Collection
446
+ module SongsRepresenter
447
+ include Representable::JSON::Collection
388
448
 
389
- items extend: SongRepresenter, class: Song
390
- end
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
- [Song.new(title: "Hyper Music"), Song.new(title: "Screenager")].extend(SongsRepresenter).to_json
395
- #=> [{"title":"Hyper Music"},{"title":"Screenager"}]
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
- module SongRepresenter
405
- include Representable::YAML
467
+ ```ruby
468
+ module SongRepresenter
469
+ include Representable::YAML
406
470
 
407
- property :title
408
- property :track
409
- collection :composers, :style => :flow
410
- end
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
- song.extend(SongRepresenter).to_yaml
415
- #=>
416
- ---
417
- title: Fallout
418
- composers: [Steward Copeland, Sting]
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
- module SongRepresenter
428
- include Representable::XML
493
+ ```ruby
494
+ module SongRepresenter
495
+ include Representable::XML
429
496
 
430
- property :title, attribute: true
431
- property :track, attribute: true
432
- end
497
+ property :title, attribute: true
498
+ property :track, attribute: true
499
+ end
433
500
 
434
- Song.new(title: "American Idle").to_xml
435
- #=> <song title="American Idle" />
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
- module AlbumRepresenter
444
- include Representable::XML
511
+ ```ruby
512
+ module AlbumRepresenter
513
+ include Representable::XML
445
514
 
446
- collection :songs, :as => :song, :wrap => :songs
447
- end
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
- Album.new.to_xml #=>
452
- <album>
453
- <songs>
454
- <song>Laundry Basket</song>
455
- <song>Two Kevins</song>
456
- <song>Wright and Rong</song>
457
- </songs>
458
- </album>
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
- class Song < OpenStruct
466
- include Representable::JSON
536
+ ```ruby
537
+ class Song < OpenStruct
538
+ include Representable::JSON
467
539
 
468
- property :name
469
- end
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
- property :title, :writer => lambda { |doc, args| doc["title"] = title || original_title }
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
- property :title, :reader => lambda { |doc, args| self.title = doc["title"] || doc["name"] }
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
- property :title, :readable => false
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
- song.to_json(:include => :title)
505
- #=> {"title":"Roxanne"}
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
- module SongRepresenter
513
- include Representable::JSON
592
+ ```ruby
593
+ module SongRepresenter
594
+ include Representable::JSON
514
595
 
515
- property :title
516
- property :track, if: lambda { track > 0 }
517
- end
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
- song.to_json(minimum_track: 2)
605
+ ```ruby
606
+ song.to_json(minimum_track: 2)
607
+ ```
524
608
 
525
609
  your `:if` may process the options.
526
610
 
527
- property :track, if: lambda { |opts| track > opts[:minimum_track] }
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
- property :track, render_nil: true
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
- gem 'virtus', ">= 0.5.0"
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
- module SongRepresenter
549
- include Representable::JSON
550
- include Representable::Coercion
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
- property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
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
- class SongRepresenter < Representable::Decorator
570
- property :title, :representer_exec => true, :getter => lambda {..}
571
- end
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
@@ -25,4 +25,4 @@ module ReaderWriter
25
25
  end
26
26
  => do that for all "features" (what parts would that be?: getter/setter, reader/writer, readable/writeable )?
27
27
 
28
- * alias :extend with :decorator
28
+ * make lambda options optional (arity == 0)
@@ -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
- return represented_exec_for(:writer, doc) if options[:writer]
40
-
41
- write_fragment(doc, get)
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
- return represented_exec_for(:reader, doc) if options[:reader]
47
-
48
- read_fragment(doc) do |value|
49
- set(value)
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
- return represented_exec_for(:getter) if options[:getter]
81
- represented.send(getter)
80
+ represented_exec_for(:getter) do
81
+ represented.send(getter)
82
+ end
82
83
  end
83
84
 
84
85
  def set(value)
85
- return represented_exec_for(:setter, value) if options[:setter]
86
- represented.send(setter, value)
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
- lambda_context.instance_exec(*args+[user_options], &options[option_name])
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_*.
@@ -1,3 +1,3 @@
1
1
  module Representable
2
- VERSION = "1.4.2"
2
+ VERSION = "1.5.0"
3
3
  end
@@ -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| compute_representer(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 { |name| obj }
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| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
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| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
606
- :class => lambda { |fragment| fragment == "Still Failing?" ? String : UpcaseString }
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 { |name| Module.new { include Representable; def from_hash(data, *args); data; end } },
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| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
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| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
651
- :class => lambda { |fragment| fragment == "Still Failing?" ? String : UpcaseString }
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.2
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-02 00:00:00.000000000 Z
12
+ date: 2013-05-04 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri