representable 1.6.1 → 1.7.0
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 -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
|
|