representable 2.4.0.rc3 → 2.4.0.rc4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,277 @@
1
+ require "test_helper"
2
+
3
+ class PipelineTest < MiniTest::Spec
4
+ Song = Struct.new(:title, :artist)
5
+ Artist = Struct.new(:name)
6
+ Album = Struct.new(:ratings, :artists)
7
+
8
+ R = Representable
9
+ P = R::Pipeline
10
+
11
+ Getter = ->(input, options) { "Yo" }
12
+ StopOnNil = ->(input, options) { input }
13
+ SkipRender = ->(input, *) { input == "Yo" ? input : P::Stop }
14
+
15
+ Prepare = ->(input, options) { "Prepare(#{input})" }
16
+ Deserialize = ->(input, options) { "Deserialize(#{input}, #{options[:fragment]})" }
17
+
18
+ SkipParse = ->(input, options) { input }
19
+ CreateObject = ->(input, options) { OpenStruct.new }
20
+
21
+
22
+ Setter = ->(input, options) { "Setter(#{input})" }
23
+
24
+ AssignFragment = ->(input, options) { options[:fragment] = input }
25
+
26
+ it "linear" do
27
+ P[SkipParse, Setter].("doc", {fragment: 1}).must_equal "Setter(doc)"
28
+
29
+
30
+ # parse style.
31
+ P[AssignFragment, SkipParse, CreateObject, Prepare].("Bla", {}).must_equal "Prepare(#<OpenStruct>)"
32
+
33
+
34
+ # render style.
35
+ P[Getter, StopOnNil, SkipRender, Prepare, Setter].(nil, {}).
36
+ must_equal "Setter(Prepare(Yo))"
37
+
38
+ # pipeline = Representable::Pipeline[SkipParse , SetResult, ModifyResult]
39
+ # pipeline.(fragment: "yo!").must_equal "modified object from yo!"
40
+ end
41
+
42
+ Stopping = ->(input, options) { return P::Stop if options[:fragment] == "stop!"; input }
43
+
44
+
45
+ it "stopping" do
46
+
47
+
48
+ pipeline = Representable::Pipeline[SkipParse, Stopping, Prepare]
49
+ pipeline.(nil, fragment: "oy!").must_equal "Prepare()"
50
+ pipeline.(nil, fragment: "stop!").must_equal Representable::Pipeline::Stop
51
+ end
52
+
53
+ describe "Collect" do
54
+ Reverse = ->(input, options) { input.reverse }
55
+ Add = ->(input, options) { "#{input}+" }
56
+ let(:pipeline) { R::Collect[Reverse, Add] }
57
+
58
+ it { pipeline.(["yo!", "oy!"], {}).must_equal ["!oy+", "!yo+"] }
59
+
60
+ describe "Pipeline with Collect" do
61
+ let(:pipeline) { P[Reverse, R::Collect[Reverse, Add]] }
62
+ it { pipeline.(["yo!", "oy!"], {}).must_equal ["!yo+", "!oy+"] }
63
+ end
64
+ end
65
+
66
+
67
+
68
+
69
+ ######### scalar property
70
+
71
+ let (:title) {
72
+ dfn = R::Definition.new(:title)
73
+
74
+ R::Hash::Binding.new(dfn)
75
+ }
76
+
77
+ it "rendering scalar property" do
78
+ doc = {}
79
+ P[
80
+ R::GetValue,
81
+ R::StopOnSkipable,
82
+ R::AssignName,
83
+ R::WriteFragment
84
+ ].(nil, {represented: Song.new("Lime Green"), binding: title, doc: doc}).must_equal "Lime Green"
85
+
86
+ doc.must_equal({"title"=>"Lime Green"})
87
+ end
88
+
89
+ it "parsing scalar property" do
90
+ P[
91
+ R::AssignName,
92
+ R::ReadFragment,
93
+ R::StopOnNotFound,
94
+ R::OverwriteOnNil,
95
+ # R::SkipParse,
96
+ R::SetValue,
97
+ ].extend(P::Debug).(doc={"title"=>"Eruption"}, {represented: song=Song.new("Lime Green"), binding: title, doc: doc}).must_equal "Eruption"
98
+ song.title.must_equal "Eruption"
99
+ end
100
+
101
+
102
+
103
+ module ArtistRepresenter
104
+ include Representable::Hash
105
+ property :name
106
+ end
107
+
108
+ let (:artist) {
109
+ dfn = R::Definition.new(:artist, extend: ArtistRepresenter, class: Artist)
110
+
111
+ R::Hash::Binding.new(dfn)
112
+ }
113
+
114
+ let (:song_model) { Song.new("Lime Green", Artist.new("Diesel Boy")) }
115
+
116
+ it "rendering typed property" do
117
+ doc = {}
118
+ P[
119
+ R::GetValue,
120
+ R::StopOnSkipable,
121
+ R::StopOnNil,
122
+ R::SkipRender,
123
+ R::Decorate,
124
+ R::Serialize,
125
+ R::AssignName,
126
+ R::WriteFragment
127
+ ].extend(P::Debug).(nil, {represented: song_model, binding: artist, doc: doc, options: {}}).must_equal({"name" => "Diesel Boy"})
128
+
129
+ doc.must_equal({"artist"=>{"name"=>"Diesel Boy"}})
130
+ end
131
+
132
+ it "parsing typed property" do
133
+ P[
134
+ R::AssignName,
135
+ R::ReadFragment,
136
+ R::StopOnNotFound,
137
+ R::OverwriteOnNil,
138
+ # R::SkipParse,
139
+ R::AssignFragment,
140
+ R::CreateObject::Class,
141
+ R::Decorate,
142
+ R::Deserialize,
143
+ R::SetValue,
144
+ ].extend(P::Debug).(doc={"artist"=>{"name"=>"Doobie Brothers"}}, {represented: song_model, binding: artist, doc: doc, options: {}}).must_equal model=Artist.new("Doobie Brothers")
145
+ song_model.artist.must_equal model
146
+ end
147
+
148
+
149
+ ######### collection :ratings
150
+
151
+ let (:ratings) {
152
+ dfn = R::Definition.new(:ratings, collection: true)
153
+
154
+ R::Hash::Binding::Collection.new(dfn)
155
+ }
156
+ it "render scalar collection" do
157
+ doc = {}
158
+ P[
159
+ R::GetValue,
160
+ R::StopOnSkipable,
161
+ R::Collect[
162
+ R::SkipRender,
163
+ ],
164
+ R::AssignName,
165
+ R::WriteFragment
166
+ ].extend(P::Debug).(nil, {represented: Album.new([1,2,3]), binding: ratings, doc: doc}).must_equal([1,2,3])
167
+
168
+ doc.must_equal({"ratings"=>[1,2,3]})
169
+ end
170
+
171
+ ######### collection :songs, extend: SongRepresenter
172
+ let (:artists) {
173
+ dfn = R::Definition.new(:artists, collection: true, extend: ArtistRepresenter, class: Artist)
174
+
175
+ R::Hash::Binding::Collection.new(dfn)
176
+ }
177
+ it "render typed collection" do
178
+ doc = {}
179
+ P[
180
+ R::GetValue,
181
+ R::StopOnSkipable,
182
+ R::Collect[
183
+ R::SkipRender,
184
+ R::Decorate,
185
+ R::Serialize,
186
+ ],
187
+ R::AssignName,
188
+ R::WriteFragment
189
+ ].extend(P::Debug).(nil, {represented: Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]), binding: artists, doc: doc, options: {}}).must_equal([{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}])
190
+
191
+ doc.must_equal({"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]})
192
+ end
193
+
194
+ let (:album_model) { Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]) }
195
+
196
+ it "parse typed collection" do
197
+ doc = {"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]}
198
+ P[
199
+ R::AssignName,
200
+ R::ReadFragment,
201
+ R::StopOnNotFound,
202
+ R::OverwriteOnNil,
203
+ # R::SkipParse,
204
+ R::Collect[
205
+ R::AssignFragment,
206
+ R::SkipRender,
207
+ R::CreateObject::Class,
208
+ R::Decorate,
209
+ R::Deserialize,
210
+ ],
211
+ R::SetValue,
212
+ ].extend(P::Debug).(doc, {represented: album_model, binding: artists, doc: doc, options: {}}).must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")])
213
+
214
+ album_model.artists.must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")])
215
+ end
216
+
217
+ # TODO: test with arrays, too, not "only" Pipeline instances.
218
+ describe "#Insert Pipeline[], Function, replace: OldFunction" do
219
+ let (:pipeline) { P[R::GetValue, R::StopOnSkipable, R::StopOnNil] }
220
+
221
+ it "returns Pipeline instance when passing in Pipeline instance" do
222
+ P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_be_instance_of(R::Pipeline)
223
+ end
224
+
225
+ it "replaces if exists" do
226
+ # pipeline.insert!(R::Default, replace: R::StopOnSkipable)
227
+ P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_equal P[R::GetValue, R::Default, R::StopOnNil]
228
+ pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil]
229
+ end
230
+
231
+ it "replaces Function instance" do
232
+ pipeline = P[R::Prepare, R::StopOnSkipable, R::StopOnNil]
233
+ P::Insert.(pipeline, R::Default, replace: R::Prepare).must_equal P[R::Default, R::StopOnSkipable, R::StopOnNil]
234
+ pipeline.must_equal P[R::Prepare, R::StopOnSkipable, R::StopOnNil]
235
+ end
236
+
237
+ it "does not replace when not existing" do
238
+ P::Insert.(pipeline, R::Default, replace: R::Prepare)
239
+ pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil]
240
+ end
241
+
242
+ it "applies on nested Collect" do
243
+ pipeline = P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil]
244
+
245
+ P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]"
246
+ pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil]
247
+
248
+
249
+ P::Insert.(pipeline, R::Default, replace: R::StopOnNil).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], Default]"
250
+ end
251
+
252
+ it "applies on nested Collect with Function::CreateObject" do
253
+ pipeline = P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil]
254
+
255
+ P::Insert.(pipeline, R::Default, replace: R::CreateObject).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]"
256
+ pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil]
257
+ end
258
+ end
259
+
260
+ describe "Insert.(delete: true)" do
261
+ let(:pipeline) { P[R::GetValue, R::StopOnNil] }
262
+
263
+ it do
264
+ P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[StopOnNil]"
265
+ pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, StopOnNil]"
266
+ end
267
+ end
268
+
269
+ describe "Insert.(delete: true) with Collect" do
270
+ let(:pipeline) { P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] }
271
+
272
+ it do
273
+ P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[Collect[StopOnSkipable], StopOnNil]"
274
+ pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], StopOnNil]"
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,105 @@
1
+ require "test_helper"
2
+
3
+ class PopulatorTest < Minitest::Spec
4
+ Song = Struct.new(:id)
5
+ Artist = Struct.new(:name)
6
+ Album = Struct.new(:songs, :artist)
7
+
8
+ describe "populator: ->{}" do
9
+ representer! do
10
+ collection :songs, populator: ->(input, options) { options[:represented].songs << song = Song.new; song } do
11
+ property :id
12
+ end
13
+
14
+ property :artist, populator: ->(input, options) { options[:represented].artist = Artist.new } do
15
+ property :name
16
+ end
17
+ end
18
+
19
+ let (:album) { Album.new([]) }
20
+
21
+ it do
22
+ album.extend(representer).from_hash("songs"=>[{"id"=>1}, {"id"=>2}], "artist"=>{"name"=>"Waste"})
23
+ album.inspect.must_equal "#<struct PopulatorTest::Album songs=[#<struct PopulatorTest::Song id=1>, #<struct PopulatorTest::Song id=2>], artist=#<struct PopulatorTest::Artist name=\"Waste\">>"
24
+ end
25
+ end
26
+
27
+ describe "populator: ->{}, " do
28
+
29
+ end
30
+ end
31
+
32
+ class PopulatorFindOrInstantiateTest < Minitest::Spec
33
+ Song = Struct.new(:id, :title, :uid) do
34
+ def self.find_by(attributes={})
35
+ return new(1, "Resist Stan", "abcd") if attributes[:id]==1# we should return the same object here
36
+ new
37
+ end
38
+ end
39
+
40
+ Composer = Struct.new(:song)
41
+ Composer.class_eval do
42
+ def song=(v)
43
+ @song = v
44
+ "Absolute nonsense" # this tests that the populator always returns the correct object.
45
+ end
46
+
47
+ attr_reader :song
48
+ end
49
+
50
+ describe "FindOrInstantiate with property" do
51
+ representer! do
52
+ property :song, populator: Representable::FindOrInstantiate, class: Song do
53
+ property :id
54
+ property :title
55
+ end
56
+ end
57
+
58
+ let (:album) { Composer.new.extend(representer).extend(Representable::Debug) }
59
+
60
+ it "finds by :id and creates new without :id" do
61
+ album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance"}})
62
+
63
+ album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
64
+ album.song.id.must_equal 1
65
+ album.song.uid.must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object.
66
+ end
67
+
68
+ it "creates new without :id" do
69
+ album.from_hash({"song"=>{"title"=>"Lower"}})
70
+
71
+ album.song.title.must_equal "Lower"
72
+ album.song.id.must_equal nil
73
+ album.song.uid.must_equal nil
74
+ end
75
+ end
76
+
77
+ describe "FindOrInstantiate with collection" do
78
+ representer! do
79
+ collection :songs, populator: Representable::FindOrInstantiate, class: Song do
80
+ property :id
81
+ property :title
82
+ end
83
+ end
84
+
85
+ let (:album) { Struct.new(:songs).new([]).extend(representer) }
86
+
87
+ it "finds by :id and creates new without :id" do
88
+ album.from_hash({"songs"=>[
89
+ {"id" => 1, "title"=>"Resist Stance"},
90
+ {"title"=>"Suffer"}
91
+ ]})
92
+
93
+ album.songs[0].title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
94
+ album.songs[0].id.must_equal 1
95
+ album.songs[0].uid.must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object.
96
+
97
+ album.songs[1].title.must_equal "Suffer"
98
+ album.songs[1].id.must_equal nil
99
+ album.songs[1].uid.must_equal nil
100
+ end
101
+
102
+ # TODO: test with existing collection
103
+ end
104
+
105
+ end
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ class PrepareTest < BaseTest
4
+ class PreparerClass
5
+ def initialize(object)
6
+ @object = object
7
+ end
8
+
9
+ def ==(b)
10
+ return unless b.instance_of?(PreparerClass)
11
+
12
+ object == b.object
13
+ end
14
+
15
+ attr_reader :object
16
+ end
17
+
18
+ describe "#to_hash" do # TODO: introduce :representable option?
19
+ representer! do
20
+ property :song,
21
+ :prepare => lambda { |obj, args| args.binding[:arbitrary].new(obj) },
22
+ :arbitrary => PreparerClass,
23
+ :extend => true,
24
+ :pass_options => true,
25
+ :representable => false # don't call #to_hash.
26
+ end
27
+
28
+ let (:hit) { Struct.new(:song).new(song).extend(representer) }
29
+
30
+ it "calls prepare:, nothing else" do
31
+ # render(hit).must_equal_document(output)
32
+ hit.to_hash.must_equal({"song" => PreparerClass.new(song)})
33
+ end
34
+
35
+
36
+ # it "calls #from_hash on the existing song instance, nothing else" do
37
+ # song_id = hit.song.object_id
38
+
39
+ # parse(hit, input)
40
+
41
+ # hit.song.title.must_equal "Suffer"
42
+ # hit.song.object_id.must_equal song_id
43
+ # end
44
+ end
45
+
46
+
47
+ describe "#from_hash" do
48
+ representer! do
49
+ property :song,
50
+ :prepare => lambda { |obj, args| args.binding[:arbitrary].new(obj) },
51
+ :arbitrary => PreparerClass,
52
+ #:extend => true, # TODO: typed: true would be better.
53
+ :instance => String.new, # pass_fragment
54
+ :pass_options => true,
55
+ :representable => false # don't call #to_hash.
56
+ end
57
+
58
+ let (:hit) { Struct.new(:song).new.extend(representer) }
59
+
60
+ it "calls prepare:, nothing else" do
61
+ # render(hit).must_equal_document(output)
62
+ hit.from_hash("song" => {})
63
+
64
+ hit.song.must_equal(PreparerClass.new(String.new))
65
+ end
66
+ end
67
+ end