representable 1.6.1 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +7 -0
- data/README.md +1 -1
- data/lib/representable.rb +1 -1
- data/lib/representable/TODO.getting_serious +5 -0
- data/lib/representable/binding.rb +13 -33
- data/lib/representable/bindings/hash_bindings.rb +2 -3
- data/lib/representable/bindings/xml_bindings.rb +40 -36
- data/lib/representable/bindings/yaml_bindings.rb +7 -7
- data/lib/representable/config.rb +19 -4
- data/lib/representable/definition.rb +4 -0
- data/lib/representable/deserializer.rb +63 -0
- data/lib/representable/hash/collection.rb +1 -1
- data/lib/representable/serializer.rb +18 -0
- data/lib/representable/version.rb +1 -1
- data/test/config_test.rb +104 -0
- data/test/definition_test.rb +46 -41
- data/test/generic_test.rb +161 -22
- data/test/representable_test.rb +56 -112
- data/test/test_helper.rb +28 -1
- metadata +6 -2
@@ -0,0 +1,18 @@
|
|
1
|
+
require "representable/deserializer"
|
2
|
+
|
3
|
+
module Representable
|
4
|
+
class ObjectSerializer < ObjectDeserializer
|
5
|
+
def call
|
6
|
+
return @object if @object.nil?
|
7
|
+
|
8
|
+
representable = prepare(@object)
|
9
|
+
|
10
|
+
serialize(representable, @binding.user_options)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def serialize(object, user_options)
|
15
|
+
object.send(@binding.serialize_method, user_options.merge!({:wrap => false}))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/test/config_test.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConfigTest < MiniTest::Spec
|
4
|
+
subject { Representable::Config.new }
|
5
|
+
PunkRock = Class.new
|
6
|
+
|
7
|
+
let (:definition) { Representable::Definition.new(:title) }
|
8
|
+
|
9
|
+
describe "wrapping" do
|
10
|
+
it "returns false per default" do
|
11
|
+
assert_equal nil, subject.wrap_for("Punk")
|
12
|
+
end
|
13
|
+
|
14
|
+
it "infers a printable class name if set to true" do
|
15
|
+
subject.wrap = true
|
16
|
+
assert_equal "punk_rock", subject.wrap_for(PunkRock)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can be set explicitely" do
|
20
|
+
subject.wrap = "Descendents"
|
21
|
+
assert_equal "Descendents", subject.wrap_for(PunkRock)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#cloned" do
|
26
|
+
it "clones all definitions" do
|
27
|
+
subject << obj = definition
|
28
|
+
|
29
|
+
subject.cloned.map(&:name).must_equal ["title"]
|
30
|
+
subject.cloned.first.object_id.wont_equal obj.object_id
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#<<" do
|
35
|
+
it "returns Definition" do
|
36
|
+
(subject << definition).must_equal definition
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#[]" do
|
41
|
+
before { subject << definition }
|
42
|
+
|
43
|
+
it { subject[:unknown].must_equal nil }
|
44
|
+
it { subject[:title].must_equal definition }
|
45
|
+
it { subject["title"].must_equal definition }
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "Config inheritance" do
|
49
|
+
# TODO: this section will soon be moved to uber.
|
50
|
+
describe "inheritance when including" do
|
51
|
+
# TODO: test all the below issues AND if cloning works.
|
52
|
+
module TestMethods
|
53
|
+
def representer_for(modules=[Representable], &block)
|
54
|
+
Module.new do
|
55
|
+
extend TestMethods
|
56
|
+
include *modules
|
57
|
+
module_exec(&block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
include TestMethods
|
62
|
+
|
63
|
+
it "inherits to uninitialized child" do
|
64
|
+
representer_for do # child
|
65
|
+
include(representer_for do # parent
|
66
|
+
representable_attrs.inheritable_array(:links) << "bar"
|
67
|
+
end)
|
68
|
+
end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
|
69
|
+
end
|
70
|
+
|
71
|
+
it "works with uninitialized parent" do
|
72
|
+
representer_for do # child
|
73
|
+
representable_attrs.inheritable_array(:links) << "bar"
|
74
|
+
|
75
|
+
include(representer_for do # parent
|
76
|
+
end)
|
77
|
+
end.representable_attrs.inheritable_array(:links).must_equal(["bar"])
|
78
|
+
end
|
79
|
+
|
80
|
+
it "inherits when both are initialized" do
|
81
|
+
representer_for do # child
|
82
|
+
representable_attrs.inheritable_array(:links) << "bar"
|
83
|
+
|
84
|
+
include(representer_for do # parent
|
85
|
+
representable_attrs.inheritable_array(:links) << "stadium"
|
86
|
+
end)
|
87
|
+
end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
|
88
|
+
end
|
89
|
+
|
90
|
+
it "clones parent inheritables" do # FIXME: actually we don't clone here!
|
91
|
+
representer_for do # child
|
92
|
+
representable_attrs.inheritable_array(:links) << "bar"
|
93
|
+
|
94
|
+
include(parent = representer_for do # parent
|
95
|
+
representable_attrs.inheritable_array(:links) << "stadium"
|
96
|
+
end)
|
97
|
+
|
98
|
+
parent.representable_attrs.inheritable_array(:links) << "park" # modify parent array.
|
99
|
+
|
100
|
+
end.representable_attrs.inheritable_array(:links).must_equal(["bar", "stadium"])
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/test/definition_test.rb
CHANGED
@@ -5,23 +5,23 @@ class DefinitionTest < MiniTest::Spec
|
|
5
5
|
before do
|
6
6
|
@def = Representable::Definition.new(:songs)
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
describe "DCI" do
|
10
10
|
it "responds to #representer_module" do
|
11
11
|
assert_equal nil, Representable::Definition.new(:song).representer_module
|
12
12
|
assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module
|
13
13
|
end
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
16
|
describe "#typed?" do
|
17
17
|
it "is false per default" do
|
18
18
|
assert ! @def.typed?
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
it "is true when :class is present" do
|
22
22
|
assert Representable::Definition.new(:songs, :class => Hash).typed?
|
23
23
|
end
|
24
|
-
|
24
|
+
|
25
25
|
it "is true when :extend is present, only" do
|
26
26
|
assert Representable::Definition.new(:songs, :extend => Hash).typed?
|
27
27
|
end
|
@@ -30,109 +30,109 @@ class DefinitionTest < MiniTest::Spec
|
|
30
30
|
assert Representable::Definition.new(:songs, :instance => Object.new).typed?
|
31
31
|
end
|
32
32
|
end
|
33
|
-
|
33
|
+
|
34
34
|
it "responds to #getter and returns string" do
|
35
35
|
assert_equal "songs", @def.getter
|
36
36
|
end
|
37
|
-
|
37
|
+
|
38
38
|
it "responds to #name" do
|
39
|
-
assert_equal "songs", @def.name
|
39
|
+
assert_equal "songs", @def.name
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it "responds to #setter" do
|
43
43
|
assert_equal :"songs=", @def.setter
|
44
44
|
end
|
45
|
-
|
45
|
+
|
46
46
|
it "responds to #sought_type" do
|
47
47
|
assert_equal nil, @def.sought_type
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
describe "#clone" do
|
51
51
|
it "clones @options" do
|
52
52
|
@def.options[:volume] = 9
|
53
53
|
cloned = @def.clone
|
54
54
|
cloned.options[:volume] = 8
|
55
|
-
|
55
|
+
|
56
56
|
assert_equal @def.options[:volume], 9
|
57
57
|
assert_equal cloned.options[:volume], 8
|
58
58
|
end
|
59
59
|
end
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
describe "#has_default?" do
|
63
63
|
it "returns false if no :default set" do
|
64
64
|
assert_equal false, Representable::Definition.new(:song).has_default?
|
65
65
|
end
|
66
|
-
|
66
|
+
|
67
67
|
it "returns true if :default set" do
|
68
68
|
assert_equal true, Representable::Definition.new(:song, :default => nil).has_default?
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
71
|
it "returns true if :collection" do
|
72
72
|
assert_equal true, Representable::Definition.new(:songs, :collection => true).has_default?
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
end
|
76
|
-
|
77
|
-
|
76
|
+
|
77
|
+
|
78
78
|
describe "#skipable_nil_value?" do
|
79
79
|
# default if skipable_nil_value?
|
80
80
|
before do
|
81
81
|
@def = Representable::Definition.new(:song, :render_nil => true)
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
it "returns false when not nil" do
|
85
85
|
assert_equal false, @def.skipable_nil_value?("Disconnect, Disconnect")
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
it "returns false when nil and :render_nil => true" do
|
89
89
|
assert_equal false, @def.skipable_nil_value?(nil)
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
it "returns true when nil and :render_nil => false" do
|
93
93
|
assert_equal true, Representable::Definition.new(:song).skipable_nil_value?(nil)
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
it "returns false when not nil and :render_nil => false" do
|
97
97
|
assert_equal false, Representable::Definition.new(:song).skipable_nil_value?("Fatal Flu")
|
98
98
|
end
|
99
99
|
end
|
100
|
-
|
101
|
-
|
100
|
+
|
101
|
+
|
102
102
|
describe "#default_for" do
|
103
103
|
before do
|
104
104
|
@def = Representable::Definition.new(:song, :default => "Insider")
|
105
105
|
end
|
106
|
-
|
106
|
+
|
107
107
|
it "always returns value when value not nil" do
|
108
108
|
assert_equal "Black And Blue", @def.default_for("Black And Blue")
|
109
109
|
end
|
110
|
-
|
110
|
+
|
111
111
|
it "returns false when value false" do
|
112
112
|
assert_equal false, @def.default_for(false)
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
it "returns default when value nil" do
|
116
116
|
assert_equal "Insider", @def.default_for(nil)
|
117
117
|
end
|
118
|
-
|
118
|
+
|
119
119
|
it "returns nil when value nil and :render_nil true" do
|
120
120
|
@def = Representable::Definition.new(:song, :render_nil => true)
|
121
121
|
assert_equal nil, @def.default_for(nil)
|
122
122
|
end
|
123
|
-
|
123
|
+
|
124
124
|
it "returns nil when value nil and :render_nil true even when :default is set" do
|
125
125
|
@def = Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")
|
126
126
|
assert_equal nil, @def.default_for(nil)
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
it "returns nil if no :default" do
|
130
130
|
@def = Representable::Definition.new(:song)
|
131
131
|
assert_equal nil, @def.default_for(nil)
|
132
132
|
end
|
133
133
|
end
|
134
|
-
|
135
|
-
|
134
|
+
|
135
|
+
|
136
136
|
describe "#writeable?" do
|
137
137
|
|
138
138
|
it "returns true when :writeable is not given" do
|
@@ -154,7 +154,7 @@ class DefinitionTest < MiniTest::Spec
|
|
154
154
|
@def = Representable::Definition.new(:song, :writeable => nil)
|
155
155
|
assert_equal nil, @def.writeable?
|
156
156
|
end
|
157
|
-
|
157
|
+
|
158
158
|
end
|
159
159
|
|
160
160
|
describe "#readable?" do
|
@@ -203,47 +203,47 @@ class DefinitionTest < MiniTest::Spec
|
|
203
203
|
before do
|
204
204
|
@def = Representable::Definition.new(:songs, :collection => true, :tag => :song)
|
205
205
|
end
|
206
|
-
|
206
|
+
|
207
207
|
it "responds to #array?" do
|
208
208
|
assert @def.array?
|
209
209
|
end
|
210
|
-
|
210
|
+
|
211
211
|
it "responds to #sought_type" do
|
212
212
|
assert_equal nil, @def.sought_type
|
213
213
|
end
|
214
|
-
|
214
|
+
|
215
215
|
it "responds to #default" do
|
216
216
|
assert_equal [], @def.send(:default)
|
217
217
|
end
|
218
218
|
end
|
219
|
-
|
219
|
+
|
220
220
|
describe ":class => Item" do
|
221
221
|
before do
|
222
222
|
@def = Representable::Definition.new(:songs, :class => Hash)
|
223
223
|
end
|
224
|
-
|
224
|
+
|
225
225
|
it "responds to #sought_type" do
|
226
226
|
assert_equal Hash, @def.sought_type
|
227
227
|
end
|
228
228
|
end
|
229
|
-
|
229
|
+
|
230
230
|
describe ":default => value" do
|
231
231
|
it "responds to #default" do
|
232
232
|
@def = Representable::Definition.new(:song)
|
233
233
|
assert_equal nil, @def.send(:default)
|
234
234
|
end
|
235
|
-
|
235
|
+
|
236
236
|
it "accepts a default value" do
|
237
237
|
@def = Representable::Definition.new(:song, :default => "Atheist Peace")
|
238
238
|
assert_equal "Atheist Peace", @def.send(:default)
|
239
239
|
end
|
240
240
|
end
|
241
|
-
|
241
|
+
|
242
242
|
describe ":hash => true" do
|
243
243
|
before do
|
244
244
|
@def = Representable::Definition.new(:songs, :hash => true)
|
245
245
|
end
|
246
|
-
|
246
|
+
|
247
247
|
it "responds to #hash?" do
|
248
248
|
assert @def.hash?
|
249
249
|
assert ! Representable::Definition.new(:songs).hash?
|
@@ -259,4 +259,9 @@ class DefinitionTest < MiniTest::Spec
|
|
259
259
|
assert_equal subject.binding, Object
|
260
260
|
end
|
261
261
|
end
|
262
|
+
|
263
|
+
describe "#sync?" do
|
264
|
+
it { Representable::Definition.new(:song).sync?.must_equal false }
|
265
|
+
it { Representable::Definition.new(:song, :parse_strategy => :sync).sync?.must_equal true }
|
266
|
+
end
|
262
267
|
end
|
data/test/generic_test.rb
CHANGED
@@ -4,7 +4,7 @@ class GenericTest < MiniTest::Spec
|
|
4
4
|
# one day, this file will contain all engine-independent test cases. one day...
|
5
5
|
let (:new_album) { OpenStruct.new.extend(representer) }
|
6
6
|
let (:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) }
|
7
|
-
let (:song) { OpenStruct.new(:title => "Resist Stance")
|
7
|
+
let (:song) { OpenStruct.new(:title => "Resist Stance") }
|
8
8
|
let (:song_representer) { Module.new do include Representable::Hash; property :title end }
|
9
9
|
|
10
10
|
|
@@ -25,12 +25,12 @@ class GenericTest < MiniTest::Spec
|
|
25
25
|
end
|
26
26
|
|
27
27
|
|
28
|
-
describe "
|
29
|
-
representer! do
|
30
|
-
property :song, :instance => lambda { |*| nil }
|
28
|
+
describe "property with instance: { nil }" do # TODO: introduce :representable option?
|
29
|
+
representer!(:inject => :song_representer) do
|
30
|
+
property :song, :instance => lambda { |*| nil }, :extend => song_representer
|
31
31
|
end
|
32
32
|
|
33
|
-
let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
|
33
|
+
let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
|
34
34
|
|
35
35
|
it "calls #to_hash on song instance, nothing else" do
|
36
36
|
hit.to_hash.must_equal("song"=>{"title"=>"Resist Stance"})
|
@@ -44,30 +44,169 @@ class GenericTest < MiniTest::Spec
|
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
+
def self.for_formats(formats)
|
48
|
+
formats.each do |format, cfg|
|
49
|
+
mod, output, input = cfg
|
50
|
+
yield format, mod, output, input
|
51
|
+
end
|
52
|
+
end
|
47
53
|
|
48
|
-
|
49
|
-
|
50
|
-
|
54
|
+
|
55
|
+
for_formats(
|
56
|
+
:hash => [Representable::Hash, {"song"=>{"title"=>"Resist Stance"}}, {"song"=>{"title"=>"Suffer"}}],
|
57
|
+
:xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><song><title>Suffer</title></song></open_struct>",],
|
58
|
+
:yaml => [Representable::YAML, "---\nsong:\n title: Resist Stance\n", "---\nsong:\n title: Suffer\n"],
|
59
|
+
) do |format, mod, output, input|
|
60
|
+
|
61
|
+
describe "[#{format}] property with parse_strategy: :sync" do # TODO: introduce :representable option?
|
62
|
+
let (:format) { format }
|
63
|
+
|
64
|
+
representer!(:module => mod, :name => :song_representer) do
|
65
|
+
property :title
|
66
|
+
self.representation_wrap = :song if format == :xml
|
67
|
+
end
|
68
|
+
|
69
|
+
representer!(:inject => :song_representer, :module => mod) do
|
70
|
+
property :song, :parse_strategy => :sync, :extend => song_representer
|
71
|
+
end
|
72
|
+
|
73
|
+
let (:hit) { hit = OpenStruct.new(:song => song).extend(representer) }
|
74
|
+
|
75
|
+
it "calls #to_hash on song instance, nothing else" do
|
76
|
+
render(hit).must_equal_document(output)
|
77
|
+
end
|
78
|
+
|
79
|
+
|
80
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
81
|
+
song_id = hit.song.object_id
|
82
|
+
|
83
|
+
parse(hit, input)
|
84
|
+
|
85
|
+
hit.song.title.must_equal "Suffer"
|
86
|
+
hit.song.object_id.must_equal song_id
|
87
|
+
end
|
51
88
|
end
|
52
|
-
|
89
|
+
end
|
90
|
+
|
91
|
+
# FIXME: there's a bug with XML and the collection name!
|
92
|
+
for_formats(
|
93
|
+
:hash => [Representable::Hash, {"songs"=>[{"title"=>"Resist Stance"}]}, {"songs"=>[{"title"=>"Suffer"}]}],
|
94
|
+
#:json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"],
|
95
|
+
:xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
|
96
|
+
:yaml => [Representable::YAML, "---\nsongs:\n- title: Resist Stance\n", "---\nsongs:\n- title: Suffer\n"],
|
97
|
+
) do |format, mod, output, input|
|
98
|
+
|
99
|
+
describe "[#{format}] collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
|
100
|
+
let (:format) { format }
|
101
|
+
representer!(:module => mod, :name => :song_representer) do
|
102
|
+
property :title
|
103
|
+
self.representation_wrap = :song if format == :xml
|
104
|
+
end
|
105
|
+
|
106
|
+
representer!(:inject => :song_representer, :module => mod) do
|
107
|
+
collection :songs, :parse_strategy => :sync, :extend => song_representer
|
108
|
+
end
|
53
109
|
|
54
|
-
|
55
|
-
|
110
|
+
let (:album) { OpenStruct.new(:songs => [song]).extend(representer) }
|
111
|
+
|
112
|
+
it "calls #to_hash on song instances, nothing else" do
|
113
|
+
render(album).must_equal_document(output)
|
114
|
+
end
|
115
|
+
|
116
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
117
|
+
collection_id = album.songs.object_id
|
118
|
+
song = album.songs.first
|
119
|
+
song_id = song.object_id
|
120
|
+
|
121
|
+
parse(album, input)
|
122
|
+
|
123
|
+
album.songs.first.title.must_equal "Suffer"
|
124
|
+
song.title.must_equal "Suffer"
|
125
|
+
#album.songs.object_id.must_equal collection_id # TODO: don't replace!
|
126
|
+
song.object_id.must_equal song_id
|
127
|
+
end
|
56
128
|
end
|
129
|
+
end
|
57
130
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
131
|
+
def render(object)
|
132
|
+
AssertableDocument.new(object.send("to_#{format}"), format)
|
133
|
+
end
|
134
|
+
|
135
|
+
def parse(object, input)
|
136
|
+
object.send("from_#{format}", input)
|
137
|
+
end
|
138
|
+
|
139
|
+
class AssertableDocument
|
140
|
+
attr_reader :document
|
141
|
+
|
142
|
+
def initialize(document, format)
|
143
|
+
@document, @format = document, format
|
144
|
+
end
|
145
|
+
|
146
|
+
def must_equal_document(*args)
|
147
|
+
return document.must_equal_xml(*args) if @format == :xml
|
148
|
+
document.must_equal(*args)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# Lonely Collection
|
154
|
+
require "representable/hash/collection"
|
155
|
+
|
156
|
+
for_formats(
|
157
|
+
:hash => [Representable::Hash::Collection, [{"title"=>"Resist Stance"}], [{"title"=>"Suffer"}]],
|
158
|
+
# :xml => [Representable::XML, "<open_struct><song><title>Resist Stance</title></song></open_struct>", "<open_struct><songs><title>Suffer</title></songs></open_struct>"],
|
159
|
+
) do |format, mod, output, input|
|
160
|
+
|
161
|
+
describe "[#{format}] lonely collection with :parse_strategy: :sync" do # TODO: introduce :representable option?
|
162
|
+
let (:format) { format }
|
163
|
+
representer!(:module => Representable::Hash, :name => :song_representer) do
|
164
|
+
property :title
|
165
|
+
self.representation_wrap = :song if format == :xml
|
166
|
+
end
|
167
|
+
|
168
|
+
representer!(:inject => :song_representer, :module => mod) do
|
169
|
+
items :parse_strategy => :sync, :extend => song_representer
|
64
170
|
end
|
65
171
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
172
|
+
let (:album) { [song].extend(representer) }
|
173
|
+
|
174
|
+
it "calls #to_hash on song instances, nothing else" do
|
175
|
+
render(album).must_equal_document(output)
|
176
|
+
end
|
177
|
+
|
178
|
+
it "calls #from_hash on the existing song instance, nothing else" do
|
179
|
+
#collection_id = album.object_id
|
180
|
+
song = album.first
|
181
|
+
song_id = song.object_id
|
182
|
+
|
183
|
+
parse(album, input)
|
184
|
+
|
185
|
+
album.first.title.must_equal "Suffer"
|
186
|
+
song.title.must_equal "Suffer"
|
187
|
+
song.object_id.must_equal song_id
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def render(object)
|
193
|
+
AssertableDocument.new(object.send("to_#{format}"), format)
|
194
|
+
end
|
195
|
+
|
196
|
+
def parse(object, input)
|
197
|
+
object.send("from_#{format}", input)
|
198
|
+
end
|
199
|
+
|
200
|
+
class AssertableDocument
|
201
|
+
attr_reader :document
|
202
|
+
|
203
|
+
def initialize(document, format)
|
204
|
+
@document, @format = document, format
|
205
|
+
end
|
206
|
+
|
207
|
+
def must_equal_document(*args)
|
208
|
+
return document.must_equal_xml(*args) if @format == :xml
|
209
|
+
document.must_equal(*args)
|
71
210
|
end
|
72
211
|
end
|
73
212
|
|