representable 2.4.0.rc3 → 2.4.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +7 -2
  3. data/Rakefile +6 -0
  4. data/lib/representable.rb +21 -17
  5. data/lib/representable/binding.rb +3 -2
  6. data/lib/representable/definition.rb +1 -1
  7. data/lib/representable/deprecations.rb +31 -5
  8. data/lib/representable/deserializer.rb +24 -34
  9. data/lib/representable/hash/collection.rb +9 -2
  10. data/lib/representable/hash_methods.rb +2 -2
  11. data/lib/representable/parse_strategies.rb +6 -7
  12. data/lib/representable/pipeline.rb +12 -1
  13. data/lib/representable/pipeline_factories.rb +9 -2
  14. data/lib/representable/serializer.rb +3 -3
  15. data/lib/representable/version.rb +1 -1
  16. data/test-with-deprecations/as_test.rb +65 -0
  17. data/test-with-deprecations/benchmarking.rb +83 -0
  18. data/test-with-deprecations/binding_test.rb +46 -0
  19. data/test-with-deprecations/blaaaaaaaa_test.rb +69 -0
  20. data/test-with-deprecations/cached_test.rb +147 -0
  21. data/test-with-deprecations/class_test.rb +119 -0
  22. data/test-with-deprecations/coercion_test.rb +52 -0
  23. data/test-with-deprecations/config/inherit_test.rb +135 -0
  24. data/test-with-deprecations/config_test.rb +122 -0
  25. data/test-with-deprecations/decorator_scope_test.rb +28 -0
  26. data/test-with-deprecations/decorator_test.rb +96 -0
  27. data/test-with-deprecations/default_test.rb +34 -0
  28. data/test-with-deprecations/defaults_options_test.rb +93 -0
  29. data/test-with-deprecations/definition_test.rb +264 -0
  30. data/test-with-deprecations/example.rb +310 -0
  31. data/test-with-deprecations/examples/object.rb +31 -0
  32. data/test-with-deprecations/exec_context_test.rb +93 -0
  33. data/test-with-deprecations/features_test.rb +70 -0
  34. data/test-with-deprecations/filter_test.rb +57 -0
  35. data/test-with-deprecations/for_collection_test.rb +74 -0
  36. data/test-with-deprecations/generic_test.rb +116 -0
  37. data/test-with-deprecations/getter_setter_test.rb +21 -0
  38. data/test-with-deprecations/hash_bindings_test.rb +87 -0
  39. data/test-with-deprecations/hash_test.rb +160 -0
  40. data/test-with-deprecations/heritage_test.rb +62 -0
  41. data/test-with-deprecations/if_test.rb +79 -0
  42. data/test-with-deprecations/include_exclude_test.rb +88 -0
  43. data/test-with-deprecations/inherit_test.rb +159 -0
  44. data/test-with-deprecations/inline_test.rb +272 -0
  45. data/test-with-deprecations/instance_test.rb +266 -0
  46. data/test-with-deprecations/is_representable_test.rb +77 -0
  47. data/test-with-deprecations/json_test.rb +355 -0
  48. data/test-with-deprecations/lonely_test.rb +239 -0
  49. data/test-with-deprecations/mongoid_test.rb +31 -0
  50. data/test-with-deprecations/nested_test.rb +115 -0
  51. data/test-with-deprecations/object_test.rb +60 -0
  52. data/{test/---deserialize-pipeline_test.rb → test-with-deprecations/parse_pipeline_test.rb} +29 -2
  53. data/test-with-deprecations/parse_strategy_test.rb +279 -0
  54. data/{test → test-with-deprecations}/pass_options_test.rb +0 -0
  55. data/test-with-deprecations/pipeline_test.rb +277 -0
  56. data/test-with-deprecations/populator_test.rb +105 -0
  57. data/test-with-deprecations/prepare_test.rb +67 -0
  58. data/test-with-deprecations/private_options_test.rb +18 -0
  59. data/test-with-deprecations/reader_writer_test.rb +19 -0
  60. data/test-with-deprecations/realistic_benchmark.rb +115 -0
  61. data/test-with-deprecations/render_nil_test.rb +21 -0
  62. data/test-with-deprecations/represent_test.rb +88 -0
  63. data/test-with-deprecations/representable_test.rb +511 -0
  64. data/test-with-deprecations/schema_test.rb +148 -0
  65. data/test-with-deprecations/serialize_deserialize_test.rb +33 -0
  66. data/test-with-deprecations/skip_test.rb +81 -0
  67. data/test-with-deprecations/stringify_hash_test.rb +41 -0
  68. data/test-with-deprecations/test_helper.rb +135 -0
  69. data/test-with-deprecations/test_helper_test.rb +25 -0
  70. data/test-with-deprecations/uncategorized_test.rb +67 -0
  71. data/test-with-deprecations/user_options_test.rb +15 -0
  72. data/test-with-deprecations/wrap_test.rb +152 -0
  73. data/test-with-deprecations/xml_bindings_test.rb +62 -0
  74. data/test-with-deprecations/xml_test.rb +503 -0
  75. data/test-with-deprecations/yaml_test.rb +162 -0
  76. data/test/as_test.rb +3 -3
  77. data/test/cached_test.rb +2 -2
  78. data/test/class_test.rb +5 -5
  79. data/test/exec_context_test.rb +2 -2
  80. data/test/filter_test.rb +1 -1
  81. data/test/getter_setter_test.rb +4 -4
  82. data/test/if_test.rb +2 -2
  83. data/test/include_exclude_test.rb +88 -0
  84. data/test/instance_test.rb +15 -15
  85. data/test/lonely_test.rb +18 -2
  86. data/test/object_test.rb +4 -4
  87. data/test/parse_pipeline_test.rb +64 -0
  88. data/test/parse_strategy_test.rb +3 -3
  89. data/test/pipeline_test.rb +8 -12
  90. data/test/prepare_test.rb +2 -3
  91. data/test/reader_writer_test.rb +3 -3
  92. data/test/representable_test.rb +12 -48
  93. data/test/serialize_deserialize_test.rb +9 -9
  94. data/test/skip_test.rb +11 -11
  95. data/test/test_helper.rb +2 -0
  96. data/test/uncategorized_test.rb +10 -10
  97. data/test/user_options_test.rb +15 -0
  98. data/test/wrap_test.rb +1 -1
  99. metadata +65 -4
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+ require 'mongoid'
3
+ require 'mongoid/document'
4
+
5
+ class MongoidTest < MiniTest::Spec
6
+ describe "Mongoid compatibility" do
7
+ it "allows #to_json" do
8
+ class Profile
9
+ include Mongoid::Document
10
+ field :name
11
+ end
12
+
13
+ class Dude
14
+ include Mongoid::Document
15
+ embeds_one :profile, :class_name => "MongoidTest::Profile"
16
+ end
17
+
18
+ module ProfileRepresenter
19
+ include Representable::JSON
20
+
21
+ property :name
22
+ end
23
+
24
+ dude = Dude.new
25
+ dude.profile = Profile.new
26
+ dude.profile.name = "Kofi"
27
+
28
+ assert_equal "{\"name\":\"Kofi\"}", dude.profile.extend(ProfileRepresenter).to_json
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,115 @@
1
+ require 'test_helper'
2
+
3
+ class NestedTest < MiniTest::Spec
4
+ Album = Struct.new(:label, :owner, :amount)
5
+
6
+ for_formats(
7
+ :hash => [Representable::Hash, {"label" => {"label"=>"Epitaph", "owner"=>"Brett Gurewitz", "releases"=>{"amount"=>19}}}],
8
+ # :xml => [Representable::XML, "<open_struct></open_struct>"],
9
+ :yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n releases:\n amount: 19\n"]
10
+ ) do |format, mod, output, input|
11
+
12
+ [false, true].each do |is_decorator|
13
+ describe "::nested with (inline representer|decorator): #{is_decorator}" do
14
+ let (:format) { format }
15
+
16
+ representer!(:module => mod, :decorator => is_decorator) do
17
+ nested :label do
18
+ property :label
19
+ property :owner
20
+
21
+ # self.representation_wrap = nil if format == :xml
22
+ nested :releases do
23
+ property :amount
24
+ end
25
+ end
26
+
27
+ # self.representation_wrap = :album if format == :xml
28
+ end
29
+
30
+ let (:album) { Album.new("Epitaph", "Brett Gurewitz", 19) }
31
+ let (:decorator) { representer.prepare(album) }
32
+
33
+ it "renders nested Album-properties in separate section" do
34
+ render(decorator).must_equal_document output
35
+
36
+ # do not use extend on the nested object. # FIXME: make this a proper test with two describes instead of this pseudo-meta stuff.
37
+ if is_decorator==true
38
+ album.wont_be_kind_of(Representable::Hash)
39
+ end
40
+ end
41
+
42
+ it "parses nested properties to Album instance" do
43
+ album = parse(representer.prepare(Album.new), output)
44
+ album.label.must_equal "Epitaph"
45
+ album.owner.must_equal "Brett Gurewitz"
46
+ end
47
+ end
48
+ end
49
+
50
+
51
+ describe "Decorator ::nested with extend:" do
52
+ let (:format) { format }
53
+
54
+ representer!(:name => :label_rpr) do
55
+ include mod
56
+ property :label
57
+ property :owner
58
+
59
+ nested :releases do # DISCUSS: do we need to test this?
60
+ property :amount
61
+ end
62
+ end
63
+
64
+ representer!(:module => mod, :decorator => true, :inject => :label_rpr) do
65
+ nested :label, :extend => label_rpr
66
+
67
+ self.representation_wrap = :album if format == :xml
68
+ end
69
+
70
+ let (:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) }
71
+
72
+ # TODO: shared example with above.
73
+ it "renders nested Album-properties in separate section" do
74
+ render(album).must_equal_document output
75
+ end
76
+
77
+ it "parses nested properties to Album instance" do
78
+ album = parse(representer.prepare(Album.new), output)
79
+ album.label.must_equal "Epitaph"
80
+ album.owner.must_equal "Brett Gurewitz"
81
+ album.amount.must_equal 19
82
+ end
83
+ end
84
+ end
85
+
86
+
87
+ describe "::nested without block but with inherit:" do
88
+
89
+ representer!(:name => :parent) do
90
+ include Representable::Hash
91
+
92
+ nested :label do
93
+ property :owner
94
+ end
95
+ end
96
+
97
+ representer!(:module => Representable::Hash, :inject => :parent) do
98
+ include parent
99
+ nested :label, :inherit => true, :as => "Label"
100
+ end
101
+
102
+ let (:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) }
103
+
104
+ it "renders nested Album-properties in separate section" do
105
+ representer.prepare(album).to_hash.must_equal({"Label"=>{"owner"=>"Brett Gurewitz"}})
106
+ end
107
+
108
+ # it "parses nested properties to Album instance" do
109
+ # album = parse(representer.prepare(Album.new), output)
110
+ # album.label.must_equal "Epitaph"
111
+ # album.owner.must_equal "Brett Gurewitz"
112
+ # album.amount.must_equal 19
113
+ # end
114
+ end
115
+ end
@@ -0,0 +1,60 @@
1
+ require "test_helper"
2
+ require "representable/object"
3
+
4
+ class ObjectTest < MiniTest::Spec
5
+ Song = Struct.new(:title, :album)
6
+ Album = Struct.new(:name, :songs)
7
+
8
+ representer!(module: Representable::Object) do
9
+ property :title
10
+
11
+ property :album, instance: lambda { |fragment, *| fragment.name.upcase!; fragment } do
12
+ property :name
13
+
14
+ collection :songs, instance: lambda { |fragment, *| fragment.title.upcase!; fragment } do
15
+ property :title
16
+ end
17
+ end
18
+ # TODO: collection
19
+ end
20
+
21
+ let (:source) { Song.new("The King Is Dead", Album.new("Ruiner", [Song.new("In Vino Veritas II")])) }
22
+ let (:target) { Song.new }
23
+
24
+ it do
25
+ representer.prepare(target).from_object(source)
26
+
27
+ target.title.must_equal "The King Is Dead"
28
+ target.album.name.must_equal "RUINER"
29
+ target.album.songs[0].title.must_equal "IN VINO VERITAS II"
30
+ end
31
+
32
+ # ignore nested object when nil
33
+ it do
34
+ representer.prepare(Song.new("The King Is Dead")).from_object(Song.new)
35
+
36
+ target.title.must_equal nil # scalar property gets overridden when nil.
37
+ target.album.must_equal nil # nested property stays nil.
38
+ end
39
+
40
+ # to_object
41
+ describe "#to_object" do
42
+ representer!(module: Representable::Object) do
43
+ property :title
44
+
45
+ property :album, render_filter: lambda { |object, *| object.name = "Live"; object } do
46
+ property :name
47
+
48
+ collection :songs, render_filter: lambda { |object, *| object[0].title = 1; object } do
49
+ property :title
50
+ end
51
+ end
52
+ end
53
+
54
+ it do
55
+ representer.prepare(source).to_object
56
+ source.album.name.must_equal "Live"
57
+ source.album.songs[0].title.must_equal 1
58
+ end
59
+ end
60
+ end
@@ -1,10 +1,37 @@
1
1
  require "test_helper"
2
2
 
3
- class DeserializePipelineTest < MiniTest::Spec
4
- Album = Struct.new(:artist, :songs)
3
+ class ParsePipelineTest < MiniTest::Spec
4
+ Album = Struct.new(:id, :artist, :songs)
5
5
  Artist = Struct.new(:email)
6
6
  Song = Struct.new(:title)
7
7
 
8
+ describe "transforming nil to [] when parsing" do
9
+ representer!(decorator: true) do
10
+ collection :songs,
11
+ parse_pipeline: ->(*) {
12
+ Representable::Pipeline.insert(
13
+ parse_functions, # original function list from Binding#parse_functions.
14
+ ->(input, options) { input.nil? ? [] : input }, # your new function (can be any callable object)..
15
+ replace: Representable::OverwriteOnNil # ..that replaces the original function.
16
+ )
17
+ },
18
+ class: Song do
19
+ property :title
20
+ end
21
+ end
22
+
23
+ it do
24
+ representer.new(album = Album.new).from_hash("songs"=>nil)
25
+ album.songs.must_equal []
26
+ end
27
+
28
+ it do
29
+ representer.new(album = Album.new).from_hash("songs"=>[{"title" => "Business Conduct"}])
30
+ album.songs.must_equal [Song.new("Business Conduct")]
31
+ end
32
+ end
33
+
34
+
8
35
  # tests [Collect[Instance, Prepare, Deserialize], Setter]
9
36
  class Representer < Representable::Decorator
10
37
  include Representable::Hash
@@ -0,0 +1,279 @@
1
+ require 'test_helper'
2
+
3
+ # parse_strategy: :sync
4
+ # parse_strategy: :replace
5
+ # parse_strategy: :find_or_instantiate ("expand" since we don't delete existing unmatched in target)
6
+
7
+
8
+ class ParseStrategySyncTest < BaseTest
9
+ for_formats(
10
+ :hash => [Representable::Hash, {"song"=>{"title"=>"Resist Stance"}}, {"song"=>{"title"=>"Suffer"}}],
11
+ :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><song><title>Suffer</title></song></open_struct>",],
12
+ :yaml => [Representable::YAML, "---\nsong:\n title: Resist Stance\n", "---\nsong:\n title: Suffer\n"],
13
+ ) do |format, mod, output, input|
14
+
15
+ describe "[#{format}] property with parse_strategy: :sync" do # TODO: introduce :representable option?
16
+ let (:format) { format }
17
+
18
+ representer!(:module => mod, :name => :song_representer) do
19
+ property :title
20
+ self.representation_wrap = :song if format == :xml
21
+ end
22
+
23
+ representer!(:inject => :song_representer, :module => mod) do
24
+ property :song, :parse_strategy => :sync, :extend => song_representer
25
+ end
26
+
27
+ let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
28
+
29
+ it "calls #to_hash on song instance, nothing else" do
30
+ render(hit).must_equal_document(output)
31
+ end
32
+
33
+
34
+ it "calls #from_hash on the existing song instance, nothing else" do
35
+ song_id = hit.song.object_id
36
+
37
+ parse(hit, input)
38
+
39
+ hit.song.title.must_equal "Suffer"
40
+ hit.song.object_id.must_equal song_id
41
+ end
42
+ end
43
+ end
44
+
45
+ # FIXME: there's a bug with XML and the collection name!
46
+ for_formats(
47
+ :hash => [Representable::Hash, {"songs"=>[{"title"=>"Resist Stance"}]}, {"songs"=>[{"title"=>"Suffer"}]}],
48
+ #:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
49
+ :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
50
+ :yaml => [Representable::YAML, "---\nsongs:\n- title: Resist Stance\n", "---\nsongs:\n- title: Suffer\n"],
51
+ ) do |format, mod, output, input|
52
+
53
+ describe "[#{format}] collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
54
+ let (:format) { format }
55
+ representer!(:module => mod, :name => :song_representer) do
56
+ property :title
57
+ self.representation_wrap = :song if format == :xml
58
+ end
59
+
60
+ representer!(:inject => :song_representer, :module => mod) do
61
+ collection :songs, :parse_strategy => :sync, :extend => song_representer
62
+ end
63
+
64
+ let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
65
+
66
+ it "calls #to_hash on song instances, nothing else" do
67
+ render(album).must_equal_document(output)
68
+ end
69
+
70
+ it "calls #from_hash on the existing song instance, nothing else" do
71
+ collection_id = album.songs.object_id
72
+ song = album.songs.first
73
+ song_id = song.object_id
74
+
75
+ parse(album, input)
76
+
77
+ album.songs.first.title.must_equal "Suffer"
78
+ song.title.must_equal "Suffer"
79
+ #album.songs.object_id.must_equal collection_id # TODO: don't replace!
80
+ song.object_id.must_equal song_id
81
+ end
82
+ end
83
+ end
84
+
85
+
86
+ # Sync errors, when model and incoming are not in sync.
87
+ describe ":sync with error" do
88
+ representer! do
89
+ property :song, :parse_strategy => :sync do
90
+ property :title
91
+ end
92
+ end
93
+
94
+ # object.song is nil whereas the document contains one.
95
+ it do
96
+ assert_raises Representable::DeserializeError do
97
+ OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Perpetual"}})
98
+ end
99
+ end
100
+ end
101
+
102
+
103
+
104
+ # Lonely Collection
105
+ for_formats(
106
+ :hash => [Representable::Hash::Collection, [{"title"=>"Resist Stance"}], [{"title"=>"Suffer"}]],
107
+ # :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
108
+ ) do |format, mod, output, input|
109
+
110
+ describe "[#{format}] lonely collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
111
+ let (:format) { format }
112
+ representer!(:module => Representable::Hash, :name => :song_representer) do
113
+ property :title
114
+ self.representation_wrap = :song if format == :xml
115
+ end
116
+
117
+ representer!(:inject => :song_representer, :module => mod) do
118
+ items :parse_strategy => :sync, :extend => song_representer
119
+ end
120
+
121
+ let (:album) { [song].extend(representer) }
122
+
123
+ it "calls #to_hash on song instances, nothing else" do
124
+ render(album).must_equal_document(output)
125
+ end
126
+
127
+ it "calls #from_hash on the existing song instance, nothing else" do
128
+ #collection_id = album.object_id
129
+ song = album.first
130
+ song_id = song.object_id
131
+
132
+ parse(album, input)
133
+
134
+ album.first.title.must_equal "Suffer"
135
+ song.title.must_equal "Suffer"
136
+ song.object_id.must_equal song_id
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+
143
+ class ParseStrategyFindOrInstantiateTest < BaseTest
144
+ # parse_strategy: :find_or_instantiate
145
+
146
+ Song = Struct.new(:id, :title)
147
+ Song.class_eval do
148
+ def self.find_by(attributes={})
149
+ return new(1, "Resist Stan") if attributes[:id]==1# we should return the same object here
150
+ new
151
+ end
152
+ end
153
+
154
+ representer!(:name => :song_representer) do
155
+ property :title
156
+ end
157
+
158
+
159
+ describe "collection" do
160
+ representer!(:inject => :song_representer) do
161
+ collection :songs, :parse_strategy => :find_or_instantiate, :extend => song_representer, :class => Song
162
+ end
163
+
164
+ let (:album) { Struct.new(:songs).new([]).extend(representer) }
165
+
166
+
167
+ it "replaces the existing collection with a new consisting of existing items or new items" do
168
+ songs_id = album.songs.object_id
169
+
170
+ album.from_hash({"songs"=>[{"id" => 1, "title"=>"Resist Stance"}, {"title"=>"Suffer"}]})
171
+
172
+ album.songs[0].title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
173
+ album.songs[0].id.must_equal 1
174
+ album.songs[1].title.must_equal "Suffer"
175
+ album.songs[1].id.must_equal nil
176
+
177
+ album.songs.object_id.wont_equal songs_id
178
+ end
179
+
180
+ # TODO: test with existing collection
181
+ end
182
+
183
+
184
+ describe "property" do
185
+ representer!(:inject => :song_representer) do
186
+ property :song, :parse_strategy => :find_or_instantiate, :extend => song_representer, :class => Song
187
+ end
188
+
189
+ let (:album) { Struct.new(:song).new.extend(representer) }
190
+
191
+
192
+ it "finds song by id" do
193
+ album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance"}})
194
+
195
+ album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
196
+ album.song.id.must_equal 1
197
+ end
198
+
199
+ it "creates song" do
200
+ album.from_hash({"song"=>{"title"=>"Off The Track"}})
201
+
202
+ album.song.title.must_equal "Off The Track"
203
+ album.song.id.must_equal nil
204
+ end
205
+ end
206
+
207
+
208
+ describe "property with dynamic :class" do
209
+ representer!(:inject => :song_representer) do
210
+ property :song, :parse_strategy => :find_or_instantiate, :extend => song_representer,
211
+ :class => lambda { |fragment, *args| fragment["class"] }
212
+ end
213
+
214
+ let (:album) { Struct.new(:song).new.extend(representer) }
215
+
216
+
217
+ it "finds song by id" do
218
+ album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance", "class"=>Song}})
219
+
220
+ album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
221
+ album.song.id.must_equal 1
222
+ end
223
+ end
224
+ end
225
+
226
+
227
+ class ParseStrategyLambdaTest < MiniTest::Spec
228
+ Song = Struct.new(:id, :title)
229
+ Song.class_eval do
230
+ def self.find_by(attributes={})
231
+ return new(1, "Resist Stan") if attributes[:id]==1# we should return the same object here
232
+ new
233
+ end
234
+ end
235
+
236
+ representer!(:name => :song_representer) do
237
+ property :title
238
+ end
239
+
240
+ # property with instance: lambda, using representable's setter. # TODO: that should be handled better via my api.
241
+ describe "property parse_strategy: lambda, representable: false" do
242
+ representer! do
243
+ property :title,
244
+ :instance => lambda { |fragment, options| fragment.to_s }, # this will still call song.title= "8675309".
245
+ :representable => false # don't call object.from_hash
246
+ end
247
+
248
+ let (:song) { Song.new(nil, nil) }
249
+ it { song.extend(representer).from_hash("title" => 8675309).title.must_equal "8675309" }
250
+ end
251
+
252
+
253
+ describe "collection" do
254
+ representer!(:inject => :song_representer) do
255
+ collection :songs, :parse_strategy => lambda { |fragment, i, options|
256
+ songs << song = Song.new
257
+ song
258
+ }, :extend => song_representer
259
+ end
260
+
261
+ let (:album) { Struct.new(:songs).new([Song.new(1, "A Walk")]).extend(representer) }
262
+
263
+
264
+ it "adds to existing collection" do
265
+ songs_id = album.songs.object_id
266
+
267
+ album.from_hash({"songs"=>[{"title"=>"Resist Stance"}]})
268
+
269
+ album.songs[0].title.must_equal "A Walk" # note how title is updated from "Resist Stan"
270
+ album.songs[0].id.must_equal 1
271
+ album.songs[1].title.must_equal "Resist Stance"
272
+ album.songs[1].id.must_equal nil
273
+
274
+ album.songs.object_id.must_equal songs_id
275
+ end
276
+
277
+ # TODO: test with existing collection
278
+ end
279
+ end