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.
- checksums.yaml +4 -4
- data/CHANGES.md +7 -2
- data/Rakefile +6 -0
- data/lib/representable.rb +21 -17
- data/lib/representable/binding.rb +3 -2
- data/lib/representable/definition.rb +1 -1
- data/lib/representable/deprecations.rb +31 -5
- data/lib/representable/deserializer.rb +24 -34
- data/lib/representable/hash/collection.rb +9 -2
- data/lib/representable/hash_methods.rb +2 -2
- data/lib/representable/parse_strategies.rb +6 -7
- data/lib/representable/pipeline.rb +12 -1
- data/lib/representable/pipeline_factories.rb +9 -2
- data/lib/representable/serializer.rb +3 -3
- data/lib/representable/version.rb +1 -1
- data/test-with-deprecations/as_test.rb +65 -0
- data/test-with-deprecations/benchmarking.rb +83 -0
- data/test-with-deprecations/binding_test.rb +46 -0
- data/test-with-deprecations/blaaaaaaaa_test.rb +69 -0
- data/test-with-deprecations/cached_test.rb +147 -0
- data/test-with-deprecations/class_test.rb +119 -0
- data/test-with-deprecations/coercion_test.rb +52 -0
- data/test-with-deprecations/config/inherit_test.rb +135 -0
- data/test-with-deprecations/config_test.rb +122 -0
- data/test-with-deprecations/decorator_scope_test.rb +28 -0
- data/test-with-deprecations/decorator_test.rb +96 -0
- data/test-with-deprecations/default_test.rb +34 -0
- data/test-with-deprecations/defaults_options_test.rb +93 -0
- data/test-with-deprecations/definition_test.rb +264 -0
- data/test-with-deprecations/example.rb +310 -0
- data/test-with-deprecations/examples/object.rb +31 -0
- data/test-with-deprecations/exec_context_test.rb +93 -0
- data/test-with-deprecations/features_test.rb +70 -0
- data/test-with-deprecations/filter_test.rb +57 -0
- data/test-with-deprecations/for_collection_test.rb +74 -0
- data/test-with-deprecations/generic_test.rb +116 -0
- data/test-with-deprecations/getter_setter_test.rb +21 -0
- data/test-with-deprecations/hash_bindings_test.rb +87 -0
- data/test-with-deprecations/hash_test.rb +160 -0
- data/test-with-deprecations/heritage_test.rb +62 -0
- data/test-with-deprecations/if_test.rb +79 -0
- data/test-with-deprecations/include_exclude_test.rb +88 -0
- data/test-with-deprecations/inherit_test.rb +159 -0
- data/test-with-deprecations/inline_test.rb +272 -0
- data/test-with-deprecations/instance_test.rb +266 -0
- data/test-with-deprecations/is_representable_test.rb +77 -0
- data/test-with-deprecations/json_test.rb +355 -0
- data/test-with-deprecations/lonely_test.rb +239 -0
- data/test-with-deprecations/mongoid_test.rb +31 -0
- data/test-with-deprecations/nested_test.rb +115 -0
- data/test-with-deprecations/object_test.rb +60 -0
- data/{test/---deserialize-pipeline_test.rb → test-with-deprecations/parse_pipeline_test.rb} +29 -2
- data/test-with-deprecations/parse_strategy_test.rb +279 -0
- data/{test → test-with-deprecations}/pass_options_test.rb +0 -0
- data/test-with-deprecations/pipeline_test.rb +277 -0
- data/test-with-deprecations/populator_test.rb +105 -0
- data/test-with-deprecations/prepare_test.rb +67 -0
- data/test-with-deprecations/private_options_test.rb +18 -0
- data/test-with-deprecations/reader_writer_test.rb +19 -0
- data/test-with-deprecations/realistic_benchmark.rb +115 -0
- data/test-with-deprecations/render_nil_test.rb +21 -0
- data/test-with-deprecations/represent_test.rb +88 -0
- data/test-with-deprecations/representable_test.rb +511 -0
- data/test-with-deprecations/schema_test.rb +148 -0
- data/test-with-deprecations/serialize_deserialize_test.rb +33 -0
- data/test-with-deprecations/skip_test.rb +81 -0
- data/test-with-deprecations/stringify_hash_test.rb +41 -0
- data/test-with-deprecations/test_helper.rb +135 -0
- data/test-with-deprecations/test_helper_test.rb +25 -0
- data/test-with-deprecations/uncategorized_test.rb +67 -0
- data/test-with-deprecations/user_options_test.rb +15 -0
- data/test-with-deprecations/wrap_test.rb +152 -0
- data/test-with-deprecations/xml_bindings_test.rb +62 -0
- data/test-with-deprecations/xml_test.rb +503 -0
- data/test-with-deprecations/yaml_test.rb +162 -0
- data/test/as_test.rb +3 -3
- data/test/cached_test.rb +2 -2
- data/test/class_test.rb +5 -5
- data/test/exec_context_test.rb +2 -2
- data/test/filter_test.rb +1 -1
- data/test/getter_setter_test.rb +4 -4
- data/test/if_test.rb +2 -2
- data/test/include_exclude_test.rb +88 -0
- data/test/instance_test.rb +15 -15
- data/test/lonely_test.rb +18 -2
- data/test/object_test.rb +4 -4
- data/test/parse_pipeline_test.rb +64 -0
- data/test/parse_strategy_test.rb +3 -3
- data/test/pipeline_test.rb +8 -12
- data/test/prepare_test.rb +2 -3
- data/test/reader_writer_test.rb +3 -3
- data/test/representable_test.rb +12 -48
- data/test/serialize_deserialize_test.rb +9 -9
- data/test/skip_test.rb +11 -11
- data/test/test_helper.rb +2 -0
- data/test/uncategorized_test.rb +10 -10
- data/test/user_options_test.rb +15 -0
- data/test/wrap_test.rb +1 -1
- metadata +65 -4
File without changes
|
@@ -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
|