representable 1.7.7 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +42 -8
  3. data/README.md +208 -55
  4. data/Rakefile +0 -6
  5. data/lib/representable.rb +39 -43
  6. data/lib/representable/binding.rb +59 -37
  7. data/lib/representable/bindings/hash_bindings.rb +3 -4
  8. data/lib/representable/bindings/xml_bindings.rb +10 -10
  9. data/lib/representable/bindings/yaml_bindings.rb +2 -2
  10. data/lib/representable/coercion.rb +1 -1
  11. data/lib/representable/config.rb +11 -5
  12. data/lib/representable/definition.rb +67 -35
  13. data/lib/representable/deserializer.rb +23 -27
  14. data/lib/representable/hash.rb +15 -4
  15. data/lib/representable/hash/allow_symbols.rb +27 -0
  16. data/lib/representable/json.rb +0 -1
  17. data/lib/representable/json/collection.rb +0 -2
  18. data/lib/representable/mapper.rb +6 -13
  19. data/lib/representable/parse_strategies.rb +57 -0
  20. data/lib/representable/readable_writeable.rb +29 -0
  21. data/lib/representable/serializer.rb +9 -4
  22. data/lib/representable/version.rb +1 -1
  23. data/lib/representable/xml.rb +1 -1
  24. data/lib/representable/xml/collection.rb +0 -2
  25. data/lib/representable/yaml.rb +0 -1
  26. data/representable.gemspec +1 -0
  27. data/test/as_test.rb +43 -0
  28. data/test/class_test.rb +124 -0
  29. data/test/config_test.rb +13 -3
  30. data/test/decorator_scope_test.rb +28 -0
  31. data/test/definition_test.rb +46 -35
  32. data/test/exec_context_test.rb +93 -0
  33. data/test/generic_test.rb +0 -154
  34. data/test/getter_setter_test.rb +28 -0
  35. data/test/hash_bindings_test.rb +35 -35
  36. data/test/hash_test.rb +0 -20
  37. data/test/if_test.rb +78 -0
  38. data/test/inherit_test.rb +21 -1
  39. data/test/inheritance_test.rb +1 -1
  40. data/test/inline_test.rb +40 -2
  41. data/test/instance_test.rb +286 -0
  42. data/test/is_representable_test.rb +77 -0
  43. data/test/json_test.rb +6 -29
  44. data/test/nested_test.rb +30 -0
  45. data/test/parse_strategy_test.rb +249 -0
  46. data/test/pass_options_test.rb +27 -0
  47. data/test/prepare_test.rb +67 -0
  48. data/test/reader_writer_test.rb +19 -0
  49. data/test/representable_test.rb +25 -265
  50. data/test/stringify_hash_test.rb +41 -0
  51. data/test/test_helper.rb +12 -4
  52. data/test/wrap_test.rb +48 -0
  53. data/test/xml_bindings_test.rb +37 -37
  54. data/test/xml_test.rb +14 -14
  55. metadata +94 -30
  56. data/lib/representable/deprecations.rb +0 -4
  57. data/lib/representable/feature/readable_writeable.rb +0 -30
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 453857338b438b7d68ffcdc22c4a673132d3193e
4
- data.tar.gz: 19635dc3d647a965fcfe0af872db31f3eb305db3
3
+ metadata.gz: b00de985635f525b8a01171885aed414e6204034
4
+ data.tar.gz: dc62dd3c718b5969a663da9587ef815ddb4eb6e8
5
5
  SHA512:
6
- metadata.gz: e20ce7e46c5a94ab219a8936c04fa1a6ed7c1f77f2be36fab23685b80c1fda109c950dd011ae35c5a638ba04e0ce739f64d612d4a01c826bc75bcdee16524e2b
7
- data.tar.gz: 58791da0eef9d17e2c3f24c82fc9c532cbfdea78d159cd09be33bf91755257adb0dbf9cb889c4facc81a17a81995ae8d5e4712063aaf4549ab571db12d9426c4
6
+ metadata.gz: 9a7cc3fb19f4319e69cbf66e566ae70b9a8ede10bc5b9666756128ab7795eec2c35315f31603ff3ad3fa8b78ac170270389ba33e128b6848496d45fd1cec615f
7
+ data.tar.gz: d2eb8e52c0485aebea0fe956fa98ef9d9c06f87a8d00a0d027dbea804b573982b364d1bcc06f3db65bbeb33e89e814a594300302675776be11024ce335569d69
data/CHANGES.md CHANGED
@@ -1,11 +1,45 @@
1
- h2. 1.8.0
2
-
3
- * Major API change: Remove defaults for collections. This fixes a major design flaw - when parsing a document a collection would be reset to `[]` even if it is not present in the parsed document.
4
- * Minor API change: Rename `Definition#sought_type` to `#deserialize_class`.
5
- -> constantize :class etc (requires AS)
6
- -> make all options lambda-able
7
- -> make major steps lambda-able
8
- -> strategies for deserialization (lambda-able!)
1
+ # 1.8.0
2
+
3
+ ## Major Breakage
4
+
5
+ * `:if` receives block arguments just like any other dynamic options. Refer to **Dynamic Options**.
6
+ * Remove defaults for collections. This fixes a major design flaw - when parsing a document a collection would be reset to `[]` even if it is not present in the parsed document.
7
+ * The number of arguments per block might have changed. Generally, if you're not interested in block arguments, use `Proc.new` or `lambda { |*| }`. See **Dynamic Options**.
8
+
9
+
10
+ ## Dynamic Options
11
+
12
+ * The following options are dynamic now and can either be a static value, a lambda or an instance method symbol: `:as`, `:getter`, `:setter`, `:class`, `:instance`, `:reader`, `:writer`, `:extend`, `:prepare`, `:if`. Please refer to the README to see their signatures.
13
+ * `representation_wrap` is dynamic, too, allowing you to change the wrap per instance.
14
+
15
+
16
+ ## Cool New Stuff
17
+
18
+ * When unsure about the number of arguments passed into an option lambda, use `:pass_options`. This passes all general options in a dedicated `Options` object that responds to `binding`, `decorator`, `represented` and `user_options`. It's always the last argument for the block.
19
+ * Added `parse_strategy: :find_or_instantiate`. More to come.
20
+ * Use `representable: false` to prevent calling `to_*/from_*` on a represented object even if the property is `typed?` (`:extend`, `:class` or `:instance` set).
21
+ * Introduced `:use_decorator` option to force an inline representer to be implemented with a Decorator even in a module. This fixes a bug since we used the `:decorate` option in earlier versions, which was already used for something else.
22
+ * Autoload `Representable::Hash*` and `Representable::Decorator`.
23
+ * Added `Representable::Hash::AllowSymbols` to convert symbol keys to strings in `from_hash`.
24
+
25
+
26
+ ## Deprecations
27
+ * `decorator_scope: true` is deprecated, use `exec_context: :decorator` instead.
28
+ * Using `:extend` in combination with an inline representer is deprecated. Include the module in the block.
29
+ * `instance: lambda { true }` is deprecated. Use `parse_strategy: :sync`.
30
+ * Removed `Config#wrap`. Only way to retrieve the evaluated wrap is `Config#wrap_for`.
31
+ * `class: lambda { nil }` is deprecated. To return the fragment from parsing, use `instance: lambda { |fragment, *args| fragment }` instead.
32
+
33
+ ## Definition
34
+
35
+ * Make `Definition < Hash`, all options can/should now be accessed with `Definition#[]`.
36
+ * Make `Definition::new` and `#merge!` the only entry points so that a `Definition` becomes an almost *immutual* object. If you happened to modify a definition using `options[..]=` this will break now. Use `definition.merge!(..)` to change it after creation.
37
+ * Deprecated `#options` as the definition itself is a hash (e.g. `definition[:default]`).
38
+ * Removed `#sought_type`, `#default`, `#attribute`, `#content`.
39
+ * `#from` is replaced by `#as` and hardcore deprecated.
40
+ * `#name` and `#as` are _always_ strings.
41
+ * A Definition is considered typed as soon as [`:extend`|`:class`|`:instance`] is set. In earlier versions, `property :song, class: Song` was considered typed, whereas `property :song, class: lambda { Song }` was static.
42
+
9
43
 
10
44
  h2. 1.7.7
11
45
 
data/README.md CHANGED
@@ -174,9 +174,28 @@ Album.new.extend(AlbumRepresenter).
174
174
  #=> #<Album name="Offspring", songs=[#<Song title="Genocide">, #<Song title="Nitro", composers=["Offspring"]>]>
175
175
  ```
176
176
 
177
+
178
+ ## Parse Strategies
179
+
180
+ When parsing `collection`s (also applies to single `property`s), representable usually iterates the incoming list and creates a new object per array item.
181
+
182
+ Parse strategies let you do that manually.
183
+
184
+ ```ruby
185
+ collection :songs, :parse_strategy => lambda { |fragment, i, options|
186
+ songs << song = Song.new
187
+ song
188
+ }
189
+ ```
190
+
191
+ 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).
192
+
193
+ This gives you all the freedom you need for your nested parsing.
194
+
195
+
177
196
  ## Syncing Objects
178
197
 
179
- Usually, representable creates a new nested object when parsing. If you want to update an existing object, use the `parse_strategy` option.
198
+ Usually, representable creates a new nested object when parsing. If you want to update an existing object, use the `parse_strategy: :sync` option.
180
199
 
181
200
  ```ruby
182
201
  module AlbumRepresenter
@@ -203,6 +222,32 @@ album.songs.first #=> #<Song:0x999 title: "Eruption">
203
222
 
204
223
  Now, representable didn't create a new `Song` instance but updated the existing, resulting in renaming the song.
205
224
 
225
+
226
+ ## Find-or-Create For Incoming Objects
227
+
228
+ Representable comes with another strategy called `:find_or_instantiate` which allows creating a property or collection from the incoming document.
229
+
230
+ Consider the following incoming hash.
231
+
232
+ ```ruby
233
+ {"songs" => [{"id" => 1, "title" => "American Paradox"}, {"title" => "Uncoil"}}
234
+ ```
235
+
236
+ And this representer setup.
237
+
238
+ ```ruby
239
+ collection :songs, class: Song, parse_strategy: :find_or_instantiate
240
+ ```
241
+
242
+ 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.
243
+
244
+ **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.
245
+
246
+ Check out the `ParsingStrategy` module to write your own strategy. If you find it useful, please commit it to the core library (with tests).
247
+
248
+ The current state of the `:find_or_instantiate` strategy is subject to change.
249
+
250
+
206
251
  ## Inline Representers
207
252
 
208
253
  If you don't want to maintain two separate modules when nesting representations you can define the `SongRepresenter` inline.
@@ -222,13 +267,15 @@ module AlbumRepresenter
222
267
 
223
268
  This works both for representer modules and decorators.
224
269
 
225
- You can use an inline representer along with `:extend`. The latter will automatically be included in the inline representer. This is handy if you want to inline-extend a base decorator.
270
+ An inline representer is just a Ruby module. You can include other representer modules. This is handy when having a base representer that needs to be extended in the inline block.
226
271
 
227
272
  ```ruby
228
273
  module AlbumRepresenter
229
274
  include Representable::JSON
230
275
 
231
- property :hit, extend: SongRepresenter do
276
+ property :hit do
277
+ include SongRepresenter
278
+
232
279
  property :numbers_sold
233
280
  end
234
281
  ```
@@ -315,7 +362,7 @@ That works as the method is mixed into the represented object. When adding a hel
315
362
 
316
363
  ```ruby
317
364
  class SongRepresenter < Representable::Decorator
318
- property :title, decorator_scope: true
365
+ property :title, exec_context: :decorator
319
366
 
320
367
  def title
321
368
  represented.name
@@ -323,7 +370,9 @@ class SongRepresenter < Representable::Decorator
323
370
  end
324
371
  ```
325
372
 
326
- 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 is ignored.
373
+ 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.
374
+
375
+ Possible values for this switch (formerly known as `:decorator_scope`) are `:binding`, `:decorator` and `nil`, which is the default setting where lambdas and methods are invoked in the represented context.
327
376
 
328
377
  Or use `:getter` or `:setter` to dynamically add a method for the represented object.
329
378
 
@@ -333,33 +382,6 @@ class SongRepresenter < Representable::Decorator
333
382
  ```
334
383
  As always, the block is executed in the represented object's context.
335
384
 
336
- ## XML Support
337
-
338
- While representable does a great job with JSON, it also features support for XML, YAML and pure ruby hashes.
339
-
340
- ```ruby
341
- require 'representable/xml'
342
-
343
- module SongRepresenter
344
- include Representable::XML
345
-
346
- property :title
347
- property :track
348
- collection :composers
349
- end
350
- ```
351
-
352
- For XML we just include the `Representable::XML` module.
353
-
354
- ```xml
355
- Song.new(title: "Fallout", composers: ["Steward Copeland", "Sting"]).
356
- extend(SongRepresenter).to_xml #=>
357
- <song>
358
- <title>Fallout</title>
359
- <composers>Steward Copeland</composers>
360
- <composers>Sting</composers>
361
- </song>
362
- ```
363
385
 
364
386
  ## Passing Options
365
387
 
@@ -398,6 +420,87 @@ property :title, :getter => lambda { |*| @name }
398
420
 
399
421
  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.
400
422
 
423
+
424
+ ## Dynamic Options
425
+
426
+ 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.
427
+
428
+ All user options are passed to the lambdas, e.g. when you call
429
+
430
+ ```ruby
431
+ song.to_hash(volume: 9)
432
+ ```
433
+
434
+ the lambda invocation for `:as` would look like this.
435
+
436
+ ```ruby
437
+ property :name, as: lambda do |args|
438
+ args #=> {:volume=>9}
439
+ end
440
+ ```
441
+
442
+ ### Available Options
443
+
444
+ Here's a list of all dynamic options and their argument signature.
445
+
446
+ * `as: lambda { |args| }` ([see Aliasing](#aliasing))
447
+ * `getter: lambda { |args| }` ([see docs](#passing-options))
448
+ * `setter: lambda { |value, args| }` ([see docs](#passing-options))
449
+ * `class: lambda { |fragment, [i], args| }` ([see Nesting](#nesting))
450
+ * `extend: lambda { |object, args| }` ([see Nesting](#nesting))
451
+ * `instance: lambda { |fragment, [i], args| }` ([see Object Creation](#polymorphic-object-creation))
452
+ * `reader: lambda { |document, args| }` ([see Read And Write](#overriding-read-and-write))
453
+ * `writer: lambda { |document, args| }` ([see Read And Write](#overriding-read-and-write))
454
+ * `if: lambda { |args| }` ([see Conditions](#conditions))
455
+ * `prepare: lambda { |object, args| }` ([see docs](#rendering-and-parsing-without-extend))
456
+ * `representation_wrap` is a dynamic option, too: `self.representation_wrap = lambda do { |args| }` ([see Wrapping](#wrapping))
457
+
458
+
459
+ ### Option Arguments
460
+
461
+ 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.
462
+
463
+ ```ruby
464
+ property :title, pass_options: true, getter: lambda do |args|
465
+ args #=> <#Options>
466
+ args.binding # etc.
467
+ end
468
+ ```
469
+
470
+ The `Options` instance exposes the following readers: `#binding`, `#represented`, `#decorator` and `#user_options` which is the hash you usually have as `args`.
471
+
472
+ Option-specific arguments (e.g. `fragment`, [see here](#available-options)) are still prepended, making the `Options` object always the *last* argument.
473
+
474
+
475
+ ## XML Support
476
+
477
+ While representable does a great job with JSON, it also features support for XML, YAML and pure ruby hashes.
478
+
479
+ ```ruby
480
+ require 'representable/xml'
481
+
482
+ module SongRepresenter
483
+ include Representable::XML
484
+
485
+ property :title
486
+ property :track
487
+ collection :composers
488
+ end
489
+ ```
490
+
491
+ For XML we just include the `Representable::XML` module.
492
+
493
+ ```xml
494
+ Song.new(title: "Fallout", composers: ["Steward Copeland", "Sting"]).
495
+ extend(SongRepresenter).to_xml #=>
496
+ <song>
497
+ <title>Fallout</title>
498
+ <composers>Steward Copeland</composers>
499
+ <composers>Sting</composers>
500
+ </song>
501
+ ```
502
+
503
+
401
504
  ## Using Helpers
402
505
 
403
506
  Sometimes it's useful to override accessors to customize output or parsing.
@@ -432,7 +535,9 @@ end
432
535
 
433
536
  ## Inheritance
434
537
 
435
- To reuse existing representers you can inherit from those modules.
538
+ To reuse existing representers use inheritance.
539
+
540
+ Inheritance works by `include`ing already defined representers.
436
541
 
437
542
  ```ruby
438
543
  module CoverSongRepresenter
@@ -443,13 +548,14 @@ module CoverSongRepresenter
443
548
  end
444
549
  ```
445
550
 
446
- Inheritance works by `include`ing already defined representers.
551
+ This results in a representer with the following properties.
552
+
553
+
447
554
 
448
- ```ruby
449
- Song.new(:title => "Truth Hits Everybody", :copyright => "The Police").
450
- extend(CoverSongRepresenter).to_json
451
555
 
452
- #=> {"title":"Truth Hits Everybody","copyright":"The Police"}
556
+ ```ruby
557
+ property :title # inherited from SongRepresenter.
558
+ property :copyright
453
559
  ```
454
560
 
455
561
  With decorators, you - surprisingly - use class inheritance.
@@ -460,7 +566,6 @@ class HitRepresenter < SongRepresenter
460
566
  ```
461
567
 
462
568
 
463
-
464
569
  ## Overriding Properties
465
570
 
466
571
  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.
@@ -489,7 +594,7 @@ module SongRepresenter
489
594
  end
490
595
  ```
491
596
 
492
- You can now inherit but still override or add options.
597
+ You can now inherit properties but still override or add options.
493
598
 
494
599
  ```ruby
495
600
  module CoverSongRepresenter
@@ -500,13 +605,12 @@ module CoverSongRepresenter
500
605
  end
501
606
  ```
502
607
 
503
- This will result in a property having the following options.
608
+ Using the `:inherit`, this will result in a property having the following options.
504
609
 
505
610
  ```ruby
506
- property :title,
507
- as: :known_as, # inherited from SongRepresenter
508
- getter: lambda { .. } # added in inheriting representer.
509
- end
611
+ property :title,
612
+ as: :known_as, # inherited from SongRepresenter.
613
+ getter: lambda { .. } # added in inheriting representer.
510
614
  ```
511
615
 
512
616
  ## Inheritance With Inline Representers
@@ -546,6 +650,8 @@ property :label do
546
650
  end
547
651
  ```
548
652
 
653
+ Naturally, `:inherit` can be used within the inline representer block.
654
+
549
655
  Note that the following also works.
550
656
 
551
657
  ```ruby
@@ -559,7 +665,7 @@ end
559
665
 
560
666
  This renames the property but still inherits all the inlined configuration.
561
667
 
562
- Basically, `:inherit` copies the configuration from the parent property, then merges it 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.
668
+ 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.
563
669
 
564
670
  ## Polymorphic Extend
565
671
 
@@ -724,7 +830,7 @@ composers: [Steward Copeland, Sting]
724
830
 
725
831
  ### Mapping Tag Attributes
726
832
 
727
- You can also map properties to tag attributes in representable. This works only for the top-level node, thou (seen from the representer's perspective).
833
+ 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).
728
834
 
729
835
  ```ruby
730
836
  module SongRepresenter
@@ -921,6 +1027,7 @@ end
921
1027
 
922
1028
  Coercing values only happens when rendering or parsing a document. Representable does not create accessors in your model as `virtus` does.
923
1029
 
1030
+
924
1031
  ## Undocumented Features
925
1032
 
926
1033
  *(Please don't read this section!)*
@@ -933,6 +1040,7 @@ If you need a special binding for a property you're free to create it using the
933
1040
  property :title, :binding => lambda { |*args| JSON::TitleBinding.new(*args) }
934
1041
  ```
935
1042
 
1043
+
936
1044
  ### Syncing Parsing
937
1045
 
938
1046
  You can use the parsed document fragment directly as a representable instance by returning `nil` in `:class`.
@@ -951,32 +1059,77 @@ Representable will not attempt to create a `Song` instance for you but use the p
951
1059
 
952
1060
  Note that this is now the [official option](#syncing-objects) `:parse_strategy`.
953
1061
 
954
- ### Rendering Without Extend
955
1062
 
956
- The same goes the other way when rendering. Just provide an empty `:instance` block.
1063
+ ### Rendering And Parsing Without Extend
1064
+
1065
+ Sometimes you wanna skip the preparation step when rendering and parsing, for instance, when the object already exposes a `#to_hash`/`#from_hash` method.
957
1066
 
958
1067
  ```ruby
959
- property :song, :instance => lambda { |*| nil }
1068
+ class ParsingSong
1069
+ def from_hash(hash, *args)
1070
+ # do whatever
1071
+
1072
+ self
1073
+ end
1074
+
1075
+ def to_hash(*args)
1076
+ {}
1077
+ end
1078
+ end
960
1079
  ```
961
1080
 
962
- This will treat the `song` property instance as a representable object.
1081
+ This would work with a representer as the following.
963
1082
 
964
1083
  ```ruby
965
- hit.to_json # this will call hit.song.to_json
1084
+ property :song, :class => ParsingSong, prepare: lambda { |object| object }
966
1085
  ```
967
1086
 
968
- Rendering `collection`s works the same. Parsing doesn't work out-of-the-box, currently, as we're still unsure how to map items to fragments.
1087
+ 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.
1088
+
1089
+
1090
+ ### Skipping Rendering Or Parsing
1091
+
1092
+ You can skip to call to `#to_hash`/`#from_hash` on the prepared object by using `:representable`.
1093
+
1094
+ ```ruby
1095
+ property :song, :representable => false
1096
+ ```
1097
+
1098
+ This will run the entire serialization/deserialization _without_ calling the actual representing method on the object.
1099
+
1100
+ Extremely helpful if you wanna use representable as a data mapping tool with filtering, aliasing, etc., without the rendering and parsing part.
1101
+
1102
+
1103
+ ### Returning Arbitrary Objects When Parsing
1104
+
1105
+ 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.
1106
+
1107
+ ```ruby
1108
+ class ParsingSong
1109
+ def from_hash(hash, *args)
1110
+ [1,2,3,4]
1111
+ end
1112
+ end
1113
+ ```
1114
+
1115
+ Album.extend(AlbumRepresenter).from_hash(..).song #=> [1,2,3,4]
1116
+
1117
+ This also works with `:extend` where the specified module overwrites the parsing method (e.g. `#from_hash`).
1118
+
969
1119
 
970
1120
  ### Decorator In Module
971
1121
 
972
1122
  Inline representers defined in a module can be implemented as a decorator, thus wrapping the represented object without pollution.
973
1123
 
974
1124
  ```ruby
975
- property :label, decorator: true do
976
- ...
1125
+ property :song, use_decorator: true do
1126
+ property :title
977
1127
  end
978
1128
  ```
979
1129
 
1130
+ This is an implementation detail most people shouldn't worry about.
1131
+
1132
+
980
1133
  ## Copyright
981
1134
 
982
1135
  Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his inspiring work.