representable 1.3.5 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.textile +5 -0
- data/README.md +30 -0
- data/TODO +5 -5
- data/lib/representable.rb +21 -6
- data/lib/representable/binding.rb +14 -11
- data/lib/representable/decorator.rb +19 -0
- data/lib/representable/definition.rb +15 -15
- data/lib/representable/version.rb +1 -1
- data/test/representable_test.rb +103 -10
- metadata +3 -3
- data/test/polymorphic_test.rb +0 -45
data/CHANGES.textile
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
h2. 1.4.0
|
2
|
+
|
3
|
+
* We now have two strategies for representing: the old extend approach and the brand-new decorator which leaves represented objects untouched. See "README":https://github.com/apotonick/representable#decorator-vs-extend for details.
|
4
|
+
* Internally, either extending or decorating in the Binding now happens through the representer class method `::prepare` (i.e. `Decorator::prepare` or `Representable::prepare` for modules). That means any representer module or class must expose this class method.
|
5
|
+
|
1
6
|
h2. 1.3.5
|
2
7
|
|
3
8
|
* Added `:reader` and `:writer` to allow overriding rendering/parsing of a property fragment and to give the user access to the entire document.
|
data/README.md
CHANGED
@@ -55,6 +55,12 @@ It also adds support for parsing.
|
|
55
55
|
song = Song.new.extend(SongRepresenter).from_json(%{ {"title":"Roxanne"} })
|
56
56
|
#=> #<Song title="Roxanne", track=nil>
|
57
57
|
|
58
|
+
|
59
|
+
## Extend vs. Decorator
|
60
|
+
|
61
|
+
If you don't want representer modules to be mixed into your objects (using `#extend`) you can use the `Decorator` strategy [described below](https://github.com/apotonick/representable#decorator-vs-extend). Decorating instead of extending was introduced in 1.4.
|
62
|
+
|
63
|
+
|
58
64
|
## Aliasing
|
59
65
|
|
60
66
|
If your property name doesn't match the name in the document, use the `:as` option.
|
@@ -146,6 +152,30 @@ Parsing a documents needs both `:extend` and the `:class` option as the parser r
|
|
146
152
|
|
147
153
|
#=> #<Album name="Offspring", songs=[#<Song title="Genocide">, #<Song title="Nitro", composers=["Offspring"]>]>
|
148
154
|
|
155
|
+
## Decorator vs. Extend
|
156
|
+
|
157
|
+
People who dislike `:extend` go use the `Decorator` strategy!
|
158
|
+
|
159
|
+
class SongRepresentation < Representable::Decorator
|
160
|
+
include Representable::JSON
|
161
|
+
|
162
|
+
property :title
|
163
|
+
property :track
|
164
|
+
end
|
165
|
+
|
166
|
+
The `Decorator` constructor requires the represented object.
|
167
|
+
|
168
|
+
SongRepresentation.new(song).to_json
|
169
|
+
|
170
|
+
This will leave the `song` instance untouched as the decorator just uses public accessors to represent the hit.
|
171
|
+
|
172
|
+
In compositions you need to specify the decorators for the nested items using the `:decorator` option where you'd normally use `:extend`.
|
173
|
+
|
174
|
+
class AlbumRepresentation < Representable::Decorator
|
175
|
+
include Representable::JSON
|
176
|
+
|
177
|
+
collection :songs, :class => Song, :decorator => SongRepresentation
|
178
|
+
end
|
149
179
|
|
150
180
|
## XML Support
|
151
181
|
|
data/TODO
CHANGED
@@ -1,11 +1,9 @@
|
|
1
|
-
* Pass key/index as first block arg to :class and :
|
1
|
+
* Pass key/index as first block arg to :class and :extend
|
2
2
|
class: |key, hsh|
|
3
3
|
|
4
4
|
* Allow passing options to Binding#serialize.
|
5
5
|
serialize(.., options{:exclude => ..})
|
6
6
|
|
7
|
-
* Allow :writer and :reader / :getter/:setter
|
8
|
-
|
9
7
|
document `XML::AttributeHash` etc
|
10
8
|
|
11
9
|
* deprecate :from, make :a(lia)s authorative.
|
@@ -16,7 +14,7 @@ document `XML::AttributeHash` etc
|
|
16
14
|
|
17
15
|
* have representable-options (:include, :exclude) and user-options
|
18
16
|
|
19
|
-
|
17
|
+
* make all properties "Object-like", even arrays of strings etc.
|
20
18
|
|
21
19
|
|
22
20
|
def compile_fragment(doc)
|
@@ -25,4 +23,6 @@ module ReaderWriter
|
|
25
23
|
do whatever
|
26
24
|
super
|
27
25
|
end
|
28
|
-
=> do that for all "features" (what parts would that be?: getter/setter, reader/writer, readable/writeable )?
|
26
|
+
=> do that for all "features" (what parts would that be?: getter/setter, reader/writer, readable/writeable )?
|
27
|
+
|
28
|
+
* alias :extend with :decorator
|
data/lib/representable.rb
CHANGED
@@ -87,7 +87,7 @@ private
|
|
87
87
|
args = []
|
88
88
|
args << binding.user_options if condition.arity > 0 # TODO: remove arity check. users should know whether they pass options or not.
|
89
89
|
|
90
|
-
not instance_exec(*args, &condition)
|
90
|
+
not represented.instance_exec(*args, &condition)
|
91
91
|
end
|
92
92
|
|
93
93
|
# TODO: remove in 1.4.
|
@@ -104,17 +104,26 @@ private
|
|
104
104
|
@representable_attrs ||= self.class.representable_attrs # DISCUSS: copy, or better not?
|
105
105
|
end
|
106
106
|
|
107
|
-
def representable_bindings_for(format, options)
|
108
|
-
options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
|
109
|
-
representable_attrs.map {|attr| format.build(attr, self, options) }
|
110
|
-
end
|
111
|
-
|
112
107
|
# Returns the wrapper for the representation. Mostly used in XML.
|
113
108
|
def representation_wrap
|
114
109
|
representable_attrs.wrap_for(self.class.name)
|
115
110
|
end
|
116
111
|
|
117
112
|
private
|
113
|
+
def representable_bindings_for(format, options)
|
114
|
+
options = cleanup_options(options) # FIXME: make representable-options and user-options two different hashes.
|
115
|
+
representable_attrs.map {|attr| representable_binding_for(attr, format, options) }
|
116
|
+
end
|
117
|
+
|
118
|
+
def representable_binding_for(attribute, format, options)
|
119
|
+
# DISCUSS: shouldn't this happen in Binding?
|
120
|
+
format.build(attribute, represented, options)
|
121
|
+
end
|
122
|
+
|
123
|
+
def represented
|
124
|
+
self
|
125
|
+
end
|
126
|
+
|
118
127
|
def cleanup_options(options) # TODO: remove me.
|
119
128
|
options.reject { |k,v| [:include, :exclude].include?(k) }
|
120
129
|
end
|
@@ -143,6 +152,10 @@ private
|
|
143
152
|
end
|
144
153
|
end
|
145
154
|
|
155
|
+
def prepare(represented)
|
156
|
+
represented.extend(self) # was: PrepareStrategy::Extend.
|
157
|
+
end
|
158
|
+
|
146
159
|
|
147
160
|
module Declarations
|
148
161
|
def representable_attrs
|
@@ -227,3 +240,5 @@ private
|
|
227
240
|
end
|
228
241
|
end
|
229
242
|
end
|
243
|
+
|
244
|
+
require 'representable/decorator'
|
@@ -93,24 +93,23 @@ module Representable
|
|
93
93
|
end
|
94
94
|
|
95
95
|
|
96
|
-
# Hooks into #serialize and #deserialize to extend typed properties
|
96
|
+
# Hooks into #serialize and #deserialize to setup (extend/decorate) typed properties
|
97
97
|
# at runtime.
|
98
|
-
module
|
98
|
+
module Prepare
|
99
99
|
# Extends the object with its representer before serialization.
|
100
100
|
def serialize(*)
|
101
|
-
|
101
|
+
prepare(super)
|
102
102
|
end
|
103
103
|
|
104
104
|
def deserialize(*)
|
105
|
-
|
105
|
+
prepare(super)
|
106
106
|
end
|
107
107
|
|
108
|
-
def
|
109
|
-
|
110
|
-
object.extend(*mod)
|
111
|
-
end
|
108
|
+
def prepare(object)
|
109
|
+
return object unless mod = representer_module_for(object) # :extend.
|
112
110
|
|
113
|
-
|
111
|
+
mod = mod.first if mod.is_a?(Array) # TODO: deprecate :extend => [..]
|
112
|
+
mod.prepare(object)
|
114
113
|
end
|
115
114
|
|
116
115
|
private
|
@@ -125,8 +124,10 @@ module Representable
|
|
125
124
|
end
|
126
125
|
end
|
127
126
|
|
127
|
+
# Overrides #serialize/#deserialize to call #to_*/from_*.
|
128
|
+
# Computes :class in #deserialize. # TODO: shouldn't this be in a separate module? ObjectSerialize/ObjectDeserialize?
|
128
129
|
module Object
|
129
|
-
include Binding::
|
130
|
+
include Binding::Prepare
|
130
131
|
|
131
132
|
def serialize(object)
|
132
133
|
return object if object.nil?
|
@@ -136,7 +137,9 @@ module Representable
|
|
136
137
|
|
137
138
|
def deserialize(data)
|
138
139
|
# DISCUSS: does it make sense to skip deserialization of nil-values here?
|
139
|
-
|
140
|
+
create_object(data).tap do |obj|
|
141
|
+
super(obj).send(deserialize_method, data, @user_options)
|
142
|
+
end
|
140
143
|
end
|
141
144
|
|
142
145
|
def create_object(fragment)
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Representable
|
2
|
+
class Decorator
|
3
|
+
attr_reader :represented
|
4
|
+
|
5
|
+
def self.prepare(represented)
|
6
|
+
new(represented) # was: PrepareStrategy::Decorate.
|
7
|
+
end
|
8
|
+
|
9
|
+
include Representable # include after class methods so Decorator::prepare can't be overwritten by Representable::prepare.
|
10
|
+
|
11
|
+
def initialize(represented)
|
12
|
+
@represented = represented
|
13
|
+
end
|
14
|
+
|
15
|
+
def representable_binding_for(attr, format, options)
|
16
|
+
format.build(attr, represented, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -3,14 +3,14 @@ module Representable
|
|
3
3
|
class Definition
|
4
4
|
attr_reader :name, :options
|
5
5
|
alias_method :getter, :name
|
6
|
-
|
6
|
+
|
7
7
|
def initialize(sym, options={})
|
8
8
|
@name = sym.to_s
|
9
9
|
@options = options
|
10
|
-
|
10
|
+
|
11
11
|
@options[:default] ||= [] if array? # FIXME: move to CollectionBinding!
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
def clone
|
15
15
|
self.class.new(name, options.clone) # DISCUSS: make generic Definition.cloned_attribute that passes list to constructor.
|
16
16
|
end
|
@@ -18,48 +18,48 @@ module Representable
|
|
18
18
|
def setter
|
19
19
|
:"#{name}="
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def typed?
|
23
23
|
sought_type.is_a?(Class) or representer_module or options[:instance] # also true if only :extend is set, for people who want solely rendering.
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
def array?
|
27
27
|
options[:collection]
|
28
28
|
end
|
29
|
-
|
29
|
+
|
30
30
|
def hash?
|
31
31
|
options[:hash]
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
def sought_type
|
35
35
|
options[:class]
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
def from
|
39
39
|
(options[:from] || options[:as] || name).to_s
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
def default_for(value)
|
43
43
|
return default if skipable_nil_value?(value)
|
44
44
|
value
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
def has_default?
|
48
48
|
options.has_key?(:default)
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
def representer_module
|
52
|
-
options[:extend]
|
52
|
+
options[:extend] or options[:decorator]
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
def attribute
|
56
56
|
options[:attribute]
|
57
57
|
end
|
58
|
-
|
58
|
+
|
59
59
|
def skipable_nil_value?(value)
|
60
60
|
value.nil? and not options[:render_nil]
|
61
61
|
end
|
62
|
-
|
62
|
+
|
63
63
|
def default
|
64
64
|
options[:default]
|
65
65
|
end
|
data/test/representable_test.rb
CHANGED
@@ -303,6 +303,10 @@ class RepresentableTest < MiniTest::Spec
|
|
303
303
|
end
|
304
304
|
|
305
305
|
describe "passing options" do
|
306
|
+
class Track
|
307
|
+
attr_accessor :nr
|
308
|
+
end
|
309
|
+
|
306
310
|
module TrackRepresenter
|
307
311
|
include Representable::Hash
|
308
312
|
property :nr
|
@@ -312,25 +316,25 @@ class RepresentableTest < MiniTest::Spec
|
|
312
316
|
super
|
313
317
|
end
|
314
318
|
def from_hash(data, options)
|
315
|
-
super
|
316
|
-
|
319
|
+
super.tap do
|
320
|
+
@nr = options[:nr]
|
321
|
+
end
|
317
322
|
end
|
318
|
-
attr_accessor :nr
|
319
323
|
end
|
320
324
|
|
321
325
|
representer! do
|
322
|
-
property :track, :extend => TrackRepresenter
|
326
|
+
property :track, :extend => TrackRepresenter, :class => Track
|
323
327
|
end
|
324
328
|
|
325
329
|
describe "#to_hash" do
|
326
330
|
it "propagates to nested objects" do
|
327
|
-
Song.new("Ocean Song",
|
331
|
+
Song.new("Ocean Song", Track.new).extend(representer).to_hash(:nr => 9).must_equal({"track"=>{"nr"=>9}})
|
328
332
|
end
|
329
333
|
end
|
330
334
|
|
331
335
|
describe "#from_hash" do
|
332
336
|
it "propagates to nested objects" do
|
333
|
-
Song.new.extend(representer).from_hash({"track"=>{"nr" => "replace me"}}, :nr => 9).track.must_equal 9
|
337
|
+
Song.new.extend(representer).from_hash({"track"=>{"nr" => "replace me"}}, :nr => 9).track.nr.must_equal 9
|
334
338
|
end
|
335
339
|
end
|
336
340
|
end
|
@@ -452,6 +456,31 @@ class RepresentableTest < MiniTest::Spec
|
|
452
456
|
assert_equal "oh yes", band.fame
|
453
457
|
end
|
454
458
|
|
459
|
+
describe "executing :if lambda in represented instance context" do
|
460
|
+
representer! do
|
461
|
+
property :label, :if => lambda { signed_contract }
|
462
|
+
end
|
463
|
+
|
464
|
+
subject { OpenStruct.new(:signed_contract => false, :label => "Fat") }
|
465
|
+
|
466
|
+
it "skips when false" do
|
467
|
+
subject.extend(representer).to_hash.must_equal({})
|
468
|
+
end
|
469
|
+
|
470
|
+
it "represents when true" do
|
471
|
+
subject.signed_contract= true
|
472
|
+
subject.extend(representer).to_hash.must_equal({"label"=>"Fat"})
|
473
|
+
end
|
474
|
+
|
475
|
+
it "works with decorator" do
|
476
|
+
rpr = representer
|
477
|
+
Class.new(Representable::Decorator) do
|
478
|
+
include rpr
|
479
|
+
end.new(subject).to_hash.must_equal({})
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
|
455
484
|
describe "propagating user options to the block" do
|
456
485
|
representer! do
|
457
486
|
property :name, :if => lambda { |opts| opts[:include_name] }
|
@@ -506,12 +535,14 @@ class RepresentableTest < MiniTest::Spec
|
|
506
535
|
|
507
536
|
describe ":extend and :class" do
|
508
537
|
module UpcaseRepresenter
|
538
|
+
include Representable
|
509
539
|
def to_hash(*); upcase; end
|
510
|
-
def from_hash(hsh, *args);
|
540
|
+
def from_hash(hsh, *args); replace hsh.upcase; end # DISCUSS: from_hash must return self.
|
511
541
|
end
|
512
542
|
module DowncaseRepresenter
|
543
|
+
include Representable
|
513
544
|
def to_hash(*); downcase; end
|
514
|
-
def from_hash(hsh, *args); hsh.downcase; end
|
545
|
+
def from_hash(hsh, *args); replace hsh.downcase; end
|
515
546
|
end
|
516
547
|
class UpcaseString < String; end
|
517
548
|
|
@@ -533,7 +564,7 @@ class RepresentableTest < MiniTest::Spec
|
|
533
564
|
|
534
565
|
describe ":instance" do
|
535
566
|
obj = String.new("Fate")
|
536
|
-
mod = Module.new { def from_hash(*); self; end }
|
567
|
+
mod = Module.new { include Representable; def from_hash(*); self; end }
|
537
568
|
representer! do
|
538
569
|
property :name, :extend => mod, :instance => lambda { |name| obj }
|
539
570
|
end
|
@@ -578,7 +609,7 @@ class RepresentableTest < MiniTest::Spec
|
|
578
609
|
|
579
610
|
describe "when :class lambda returns nil" do
|
580
611
|
representer! do
|
581
|
-
property :name, :extend => lambda { |name| Module.new { def from_hash(data, *args); data; end } },
|
612
|
+
property :name, :extend => lambda { |name| Module.new { include Representable; def from_hash(data, *args); data; end } },
|
582
613
|
:class => nil
|
583
614
|
end
|
584
615
|
|
@@ -618,6 +649,16 @@ class RepresentableTest < MiniTest::Spec
|
|
618
649
|
end
|
619
650
|
end
|
620
651
|
|
652
|
+
describe ":decorator" do
|
653
|
+
let (:extend_rpr) { Module.new { include Representable::Hash; collection :songs, :extend => SongRepresenter } }
|
654
|
+
let (:decorator_rpr) { Module.new { include Representable::Hash; collection :songs, :decorator => SongRepresenter } }
|
655
|
+
let (:songs) { [Song.new("Bloody Mary")] }
|
656
|
+
|
657
|
+
it "is aliased to :extend" do
|
658
|
+
Album.new(songs).extend(extend_rpr).to_hash.must_equal Album.new(songs).extend(decorator_rpr).to_hash
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
621
662
|
describe ":binding" do
|
622
663
|
representer! do
|
623
664
|
class MyBinding < Representable::Binding
|
@@ -633,6 +674,58 @@ class RepresentableTest < MiniTest::Spec
|
|
633
674
|
end
|
634
675
|
end
|
635
676
|
|
677
|
+
describe "decorator" do
|
678
|
+
# TODO: Move to global place since it's used twice.
|
679
|
+
class SongRepresentation < Representable::Decorator
|
680
|
+
include Representable::JSON
|
681
|
+
property :name
|
682
|
+
end
|
683
|
+
|
684
|
+
class AlbumRepresentation < Representable::Decorator
|
685
|
+
include Representable::JSON
|
686
|
+
|
687
|
+
collection :songs, :class => Song, :extend => SongRepresentation
|
688
|
+
end
|
689
|
+
|
690
|
+
let (:song) { Song.new("Mama, I'm Coming Home") }
|
691
|
+
let (:album) { Album.new([song]) }
|
692
|
+
let (:decorator) { AlbumRepresentation.new(album) }
|
693
|
+
|
694
|
+
it "renders" do
|
695
|
+
decorator.to_hash.must_equal({"songs"=>[{"name"=>"Mama, I'm Coming Home"}]})
|
696
|
+
album.wont_respond_to :to_hash
|
697
|
+
song.wont_respond_to :to_hash # DISCUSS: weak test, how to assert blank slate?
|
698
|
+
end
|
699
|
+
|
700
|
+
it "parses" do
|
701
|
+
decorator.from_hash({"songs"=>[{"name"=>"Atomic Garden"}]})
|
702
|
+
album.songs.first.must_be_kind_of Song
|
703
|
+
album.songs.must_equal [Song.new("Atomic Garden")]
|
704
|
+
album.wont_respond_to :to_hash
|
705
|
+
song.wont_respond_to :to_hash # DISCUSS: weak test, how to assert blank slate?
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
describe "::prepare" do
|
710
|
+
let (:song) { Song.new("Still Friends In The End") }
|
711
|
+
let (:album) { Album.new([song]) }
|
712
|
+
|
713
|
+
describe "module including Representable" do
|
714
|
+
it "uses :extend strategy" do
|
715
|
+
album_rpr = Module.new { include Representable::Hash; collection :songs, :class => Song, :extend => SongRepresenter}
|
716
|
+
|
717
|
+
album_rpr.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
|
718
|
+
album.must_respond_to :to_hash
|
719
|
+
end
|
720
|
+
end
|
721
|
+
|
722
|
+
describe "Decorator subclass" do
|
723
|
+
it "uses :decorate strategy" do
|
724
|
+
AlbumRepresentation.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
|
725
|
+
album.wont_respond_to :to_hash
|
726
|
+
end
|
727
|
+
end
|
728
|
+
end
|
636
729
|
end
|
637
730
|
|
638
731
|
describe "Config" 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.4.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-
|
12
|
+
date: 2013-04-10 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
@@ -176,6 +176,7 @@ files:
|
|
176
176
|
- lib/representable/bindings/xml_bindings.rb
|
177
177
|
- lib/representable/bindings/yaml_bindings.rb
|
178
178
|
- lib/representable/coercion.rb
|
179
|
+
- lib/representable/decorator.rb
|
179
180
|
- lib/representable/definition.rb
|
180
181
|
- lib/representable/deprecations.rb
|
181
182
|
- lib/representable/feature/readable_writeable.rb
|
@@ -198,7 +199,6 @@ files:
|
|
198
199
|
- test/hash_test.rb
|
199
200
|
- test/json_test.rb
|
200
201
|
- test/mongoid_test.rb
|
201
|
-
- test/polymorphic_test.rb
|
202
202
|
- test/representable_test.rb
|
203
203
|
- test/test_helper.rb
|
204
204
|
- test/test_helper_test.rb
|
data/test/polymorphic_test.rb
DELETED
@@ -1,45 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
module PolymorphicExtender
|
4
|
-
def self.extended(model)
|
5
|
-
model.extend(representer_name_for(model))
|
6
|
-
end
|
7
|
-
|
8
|
-
def self.representer_name_for(model)
|
9
|
-
PolymorphicTest.const_get("#{model.class.to_s.split("::").last}Representer")
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
class PolymorphicTest < MiniTest::Spec
|
15
|
-
class PopSong < Song
|
16
|
-
end
|
17
|
-
|
18
|
-
module SongRepresenter
|
19
|
-
include Representable::JSON
|
20
|
-
property :name
|
21
|
-
end
|
22
|
-
|
23
|
-
module PopSongRepresenter
|
24
|
-
include Representable::JSON
|
25
|
-
property :name, :from => "known_as"
|
26
|
-
end
|
27
|
-
|
28
|
-
class Album
|
29
|
-
attr_accessor :songs
|
30
|
-
end
|
31
|
-
|
32
|
-
module AlbumRepresenter
|
33
|
-
include Representable::JSON
|
34
|
-
collection :songs, :extend => PolymorphicExtender
|
35
|
-
end
|
36
|
-
|
37
|
-
|
38
|
-
describe "PolymorphicExtender" do
|
39
|
-
it "extends each model with the correct representer in #to_json" do
|
40
|
-
album = Album.new
|
41
|
-
album.songs = [PopSong.new("Let Me Down"), Song.new("The 4 Horsemen")]
|
42
|
-
assert_equal "{\"songs\":[{\"known_as\":\"Let Me Down\"},{\"name\":\"The 4 Horsemen\"}]}", album.extend(AlbumRepresenter).to_json
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|