representable 2.0.4 → 2.1.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 +17 -0
- data/README.md +20 -1
- data/lib/representable.rb +2 -1
- data/lib/representable/binding.rb +115 -59
- data/lib/representable/config.rb +8 -0
- data/lib/representable/definition.rb +10 -14
- data/lib/representable/deserializer.rb +64 -25
- data/lib/representable/hash.rb +3 -3
- data/lib/representable/hash/binding.rb +40 -0
- data/lib/representable/hash/collection.rb +3 -2
- data/lib/representable/hash_methods.rb +4 -2
- data/lib/representable/mapper.rb +1 -1
- data/lib/representable/populator.rb +59 -0
- data/lib/representable/serializer.rb +24 -13
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +3 -3
- data/lib/representable/xml/binding.rb +171 -0
- data/lib/representable/yaml.rb +3 -3
- data/lib/representable/yaml/binding.rb +48 -0
- data/representable.gemspec +1 -1
- data/test/benchmarking.rb +83 -0
- data/test/binding_test.rb +46 -0
- data/test/definition_test.rb +5 -58
- data/test/exec_context_test.rb +4 -4
- data/test/hash_bindings_test.rb +4 -52
- data/test/hash_test.rb +6 -6
- data/test/json_test.rb +8 -8
- data/test/lonely_test.rb +1 -1
- data/test/realistic_benchmark.rb +83 -0
- data/test/skip_test.rb +28 -0
- data/test/xml_bindings_test.rb +2 -109
- data/test/xml_test.rb +61 -23
- data/test/yaml_test.rb +5 -8
- metadata +19 -11
- data/lib/representable/bindings/hash_bindings.rb +0 -64
- data/lib/representable/bindings/xml_bindings.rb +0 -172
- data/lib/representable/bindings/yaml_bindings.rb +0 -49
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'representable/hash/binding'
|
2
|
+
|
3
|
+
module Representable
|
4
|
+
module YAML
|
5
|
+
class Binding < Representable::Hash::Binding
|
6
|
+
def self.build_for(definition, *args)
|
7
|
+
return Collection.new(definition, *args) if definition.array?
|
8
|
+
new(definition, *args)
|
9
|
+
end
|
10
|
+
|
11
|
+
def write(map, fragment)
|
12
|
+
map.children << Psych::Nodes::Scalar.new(as)
|
13
|
+
map.children << node_for(fragment) # FIXME: should be serialize.
|
14
|
+
end
|
15
|
+
# private
|
16
|
+
|
17
|
+
def node_for(fragment)
|
18
|
+
write_scalar(fragment)
|
19
|
+
end
|
20
|
+
|
21
|
+
def write_scalar(value)
|
22
|
+
return value if typed?
|
23
|
+
|
24
|
+
Psych::Nodes::Scalar.new(value.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
def serialize_method
|
28
|
+
:to_ast
|
29
|
+
end
|
30
|
+
|
31
|
+
def deserialize_method
|
32
|
+
:from_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
class Collection < self
|
37
|
+
include Representable::Binding::Collection
|
38
|
+
|
39
|
+
def node_for(fragments)
|
40
|
+
Psych::Nodes::Sequence.new.tap do |seq|
|
41
|
+
seq.style = Psych::Nodes::Sequence::FLOW if self[:style] == :flow
|
42
|
+
fragments.each { |frag| seq.children << write_scalar(frag) }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/representable.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
|
26
26
|
s.add_development_dependency "rake"
|
27
27
|
s.add_development_dependency "test_xml", ">= 0.1.6"
|
28
|
-
s.add_development_dependency "minitest", "
|
28
|
+
s.add_development_dependency "minitest", ">= 5.4.1"
|
29
29
|
s.add_development_dependency "mocha", ">= 0.13.0"
|
30
30
|
s.add_development_dependency "mongoid"
|
31
31
|
s.add_development_dependency "virtus"
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
SONG_PROPERTIES = 1000.times.collect do |i|
|
5
|
+
"property_#{i}"
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
module SongRepresenter
|
10
|
+
include Representable::JSON
|
11
|
+
|
12
|
+
SONG_PROPERTIES.each { |p| property p }
|
13
|
+
end
|
14
|
+
|
15
|
+
class SongDecorator < Representable::Decorator
|
16
|
+
include Representable::JSON
|
17
|
+
|
18
|
+
SONG_PROPERTIES.each { |p| property p }
|
19
|
+
end
|
20
|
+
|
21
|
+
module AlbumRepresenter
|
22
|
+
include Representable::JSON
|
23
|
+
|
24
|
+
# collection :songs, extend: SongRepresenter
|
25
|
+
collection :songs, extend: SongDecorator
|
26
|
+
end
|
27
|
+
|
28
|
+
def random_song
|
29
|
+
attrs = Hash[SONG_PROPERTIES.collect { |n| [n,n] }]
|
30
|
+
OpenStruct.new(attrs)
|
31
|
+
end
|
32
|
+
|
33
|
+
times = []
|
34
|
+
|
35
|
+
3.times.each do
|
36
|
+
album = OpenStruct.new(songs: 100.times.collect { random_song })
|
37
|
+
|
38
|
+
times << Benchmark.measure do
|
39
|
+
puts "================ next!"
|
40
|
+
album.extend(AlbumRepresenter).to_json
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
puts times.join("")
|
45
|
+
|
46
|
+
# 100 songs, 100 attrs
|
47
|
+
# 0.050000 0.000000 0.050000 ( 0.093157)
|
48
|
+
|
49
|
+
## 100 songs, 1000 attrs
|
50
|
+
# 0.470000 0.010000 0.480000 ( 0.483708)
|
51
|
+
|
52
|
+
|
53
|
+
### without binding cache:
|
54
|
+
# 2.790000 0.030000 2.820000 ( 2.820190)
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
### with extend: on Song, with binding cache>
|
59
|
+
# 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0
|
60
|
+
### without skip?
|
61
|
+
# 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3
|
62
|
+
|
63
|
+
### without :writer
|
64
|
+
# 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2
|
65
|
+
### without :render_filter
|
66
|
+
# 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0
|
67
|
+
###without default_for and skipable?
|
68
|
+
# 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7
|
69
|
+
### without :serialize
|
70
|
+
# 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7
|
71
|
+
### using decorator
|
72
|
+
# 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6
|
73
|
+
### with prepare AFTER representable?
|
74
|
+
# 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3
|
75
|
+
|
76
|
+
|
77
|
+
# representable 2.0
|
78
|
+
# 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0
|
79
|
+
|
80
|
+
# no method missing
|
81
|
+
# 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5
|
82
|
+
# no def_delegator in Definition
|
83
|
+
# 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BindingTest < MiniTest::Spec
|
4
|
+
Binding = Representable::Binding
|
5
|
+
let (:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) }
|
6
|
+
|
7
|
+
describe "#skipable_empty_value?" do
|
8
|
+
let (:binding) { Binding.new(render_nil_definition, nil, nil) }
|
9
|
+
|
10
|
+
# don't skip when present.
|
11
|
+
it { binding.skipable_empty_value?("Disconnect, Disconnect").must_equal false }
|
12
|
+
|
13
|
+
# don't skip when it's nil and render_nil: true
|
14
|
+
it { binding.skipable_empty_value?(nil).must_equal false }
|
15
|
+
|
16
|
+
# skip when nil and :render_nil undefined.
|
17
|
+
it { Binding.new(Representable::Definition.new(:song), nil, nil).skipable_empty_value?(nil).must_equal true }
|
18
|
+
|
19
|
+
# don't skip when nil and :render_nil undefined.
|
20
|
+
it { Binding.new(Representable::Definition.new(:song), nil, nil).skipable_empty_value?("Fatal Flu").must_equal false }
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
describe "#default_for" do
|
25
|
+
let (:definition) { Representable::Definition.new(:song, :default => "Insider") }
|
26
|
+
let (:binding) { Binding.new(definition, nil, nil) }
|
27
|
+
|
28
|
+
# return value when value present.
|
29
|
+
it { binding.default_for("Black And Blue").must_equal "Black And Blue" }
|
30
|
+
|
31
|
+
# return false when value false.
|
32
|
+
it { binding.default_for(false).must_equal false }
|
33
|
+
|
34
|
+
# return default when value nil.
|
35
|
+
it { binding.default_for(nil).must_equal "Insider" }
|
36
|
+
|
37
|
+
# return nil when value nil and render_nil: true.
|
38
|
+
it { Binding.new(render_nil_definition, nil, nil).default_for(nil).must_equal nil }
|
39
|
+
|
40
|
+
# return nil when value nil and render_nil: true, even when :default is set" do
|
41
|
+
it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest"), nil, nil).default_for(nil).must_equal nil }
|
42
|
+
|
43
|
+
# return nil if no :default
|
44
|
+
it { Binding.new(Representable::Definition.new(:song), nil, nil).default_for(nil).must_equal nil }
|
45
|
+
end
|
46
|
+
end
|
data/test/definition_test.rb
CHANGED
@@ -88,6 +88,11 @@ class DefinitionTest < MiniTest::Spec
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
# #inspect
|
92
|
+
describe "#inspect" do
|
93
|
+
it { Definition.new(:songs).inspect.must_equal "#<Representable::Definition ==>songs @options={:parse_filter=>[], :render_filter=>[], :as=>\"songs\"}>" }
|
94
|
+
end
|
95
|
+
|
91
96
|
|
92
97
|
describe "generic API" do
|
93
98
|
before do
|
@@ -186,64 +191,6 @@ class DefinitionTest < MiniTest::Spec
|
|
186
191
|
end
|
187
192
|
|
188
193
|
|
189
|
-
describe "#skipable_empty_value?" do
|
190
|
-
# default if skipable_empty_value?
|
191
|
-
before do
|
192
|
-
@def = Representable::Definition.new(:song, :render_nil => true)
|
193
|
-
end
|
194
|
-
|
195
|
-
it "returns false when not nil" do
|
196
|
-
assert_equal false, @def.skipable_empty_value?("Disconnect, Disconnect")
|
197
|
-
end
|
198
|
-
|
199
|
-
it "returns false when nil and :render_nil => true" do
|
200
|
-
assert_equal false, @def.skipable_empty_value?(nil)
|
201
|
-
end
|
202
|
-
|
203
|
-
it "returns true when nil and :render_nil => false" do
|
204
|
-
assert_equal true, Representable::Definition.new(:song).skipable_empty_value?(nil)
|
205
|
-
end
|
206
|
-
|
207
|
-
it "returns false when not nil and :render_nil => false" do
|
208
|
-
assert_equal false, Representable::Definition.new(:song).skipable_empty_value?("Fatal Flu")
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
|
213
|
-
describe "#default_for" do
|
214
|
-
before do
|
215
|
-
@def = Representable::Definition.new(:song, :default => "Insider")
|
216
|
-
end
|
217
|
-
|
218
|
-
it "always returns value when value not nil" do
|
219
|
-
assert_equal "Black And Blue", @def.default_for("Black And Blue")
|
220
|
-
end
|
221
|
-
|
222
|
-
it "returns false when value false" do
|
223
|
-
assert_equal false, @def.default_for(false)
|
224
|
-
end
|
225
|
-
|
226
|
-
it "returns default when value nil" do
|
227
|
-
assert_equal "Insider", @def.default_for(nil)
|
228
|
-
end
|
229
|
-
|
230
|
-
it "returns nil when value nil and :render_nil true" do
|
231
|
-
@def = Representable::Definition.new(:song, :render_nil => true)
|
232
|
-
assert_equal nil, @def.default_for(nil)
|
233
|
-
end
|
234
|
-
|
235
|
-
it "returns nil when value nil and :render_nil true even when :default is set" do
|
236
|
-
@def = Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")
|
237
|
-
assert_equal nil, @def.default_for(nil)
|
238
|
-
end
|
239
|
-
|
240
|
-
it "returns nil if no :default" do
|
241
|
-
@def = Representable::Definition.new(:song)
|
242
|
-
assert_equal nil, @def.default_for(nil)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
|
247
194
|
describe "#binding" do
|
248
195
|
it "returns true when :binding is set" do
|
249
196
|
assert Representable::Definition.new(:songs, :binding => Object)[:binding]
|
data/test/exec_context_test.rb
CHANGED
@@ -40,8 +40,8 @@ class ExecContextTest < MiniTest::Spec
|
|
40
40
|
}
|
41
41
|
end
|
42
42
|
|
43
|
-
it { render(song).must_equal_document({Representable::Hash::
|
44
|
-
it { parse(song, {Representable::Hash::
|
43
|
+
it { render(song).must_equal_document({Representable::Hash::Binding => "name"}) }
|
44
|
+
it { parse(song, {Representable::Hash::Binding => "Rebel Fate"}).name.must_equal "Rebel Fate" }
|
45
45
|
end
|
46
46
|
|
47
47
|
|
@@ -85,8 +85,8 @@ class ExecContextTest < MiniTest::Spec
|
|
85
85
|
}
|
86
86
|
end
|
87
87
|
|
88
|
-
it { render(song).must_equal_document({Representable::Hash::
|
89
|
-
it { parse(song, {Representable::Hash::
|
88
|
+
it { render(song).must_equal_document({Representable::Hash::Binding => "name"}) }
|
89
|
+
it { parse(song, {Representable::Hash::Binding => "Rebel Fate"}).name.must_equal "Rebel Fate" }
|
90
90
|
end
|
91
91
|
end
|
92
92
|
end
|
data/test/hash_bindings_test.rb
CHANGED
@@ -15,7 +15,7 @@ class HashBindingTest < MiniTest::Spec
|
|
15
15
|
describe "PropertyBinding" do
|
16
16
|
describe "#read" do
|
17
17
|
before do
|
18
|
-
@property = Representable::Hash::
|
18
|
+
@property = Representable::Hash::Binding.new(Representable::Definition.new(:song), nil, nil)
|
19
19
|
end
|
20
20
|
|
21
21
|
it "returns fragment if present" do
|
@@ -29,61 +29,13 @@ class HashBindingTest < MiniTest::Spec
|
|
29
29
|
end
|
30
30
|
|
31
31
|
end
|
32
|
-
|
33
|
-
describe "with plain text" do
|
34
|
-
before do
|
35
|
-
@property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song), nil, nil)
|
36
|
-
end
|
37
|
-
|
38
|
-
it "extracts with #read" do
|
39
|
-
assert_equal "Thinning the Herd", @property.read("song" => "Thinning the Herd")
|
40
|
-
end
|
41
|
-
|
42
|
-
it "inserts with #write" do
|
43
|
-
doc = {}
|
44
|
-
assert_equal("Thinning the Herd", @property.write(doc,"Thinning the Herd"))
|
45
|
-
assert_equal({"song"=>"Thinning the Herd"}, doc)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
describe "with an object" do
|
50
|
-
before do
|
51
|
-
@property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => SongWithRepresenter), nil, nil)
|
52
|
-
@doc = {}
|
53
|
-
end
|
54
|
-
|
55
|
-
it "extracts with #read" do
|
56
|
-
assert_equal SongWithRepresenter.new("Thinning the Herd"), @property.read("song" => {"name" => "Thinning the Herd"})
|
57
|
-
end
|
58
|
-
|
59
|
-
it "inserts with #write" do
|
60
|
-
assert_equal({"name"=>"Thinning the Herd"}, @property.write(@doc, SongWithRepresenter.new("Thinning the Herd")))
|
61
|
-
assert_equal({"song" => {"name"=>"Thinning the Herd"}}, @doc)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
describe "with an object and :extend" do
|
66
|
-
before do
|
67
|
-
@property = Representable::Hash::PropertyBinding.new(Representable::Definition.new(:song, :class => Song, :extend => SongRepresenter), nil, nil)
|
68
|
-
@doc = {}
|
69
|
-
end
|
70
|
-
|
71
|
-
it "extracts with #read" do
|
72
|
-
assert_equal Song.new("Thinning the Herd"), @property.read("song" => {"name" => "Thinning the Herd"})
|
73
|
-
end
|
74
|
-
|
75
|
-
it "inserts with #write" do
|
76
|
-
assert_equal({"name"=>"Thinning the Herd"}, @property.write(@doc, Song.new("Thinning the Herd")))
|
77
|
-
assert_equal({"song" => {"name"=>"Thinning the Herd"}}, @doc)
|
78
|
-
end
|
79
|
-
end
|
80
32
|
end
|
81
33
|
|
82
34
|
|
83
35
|
describe "CollectionBinding" do
|
84
36
|
describe "with plain text items" do
|
85
37
|
before do
|
86
|
-
@property = Representable::Hash::
|
38
|
+
@property = Representable::Hash::Binding::Collection.new(Representable::Definition.new(:songs, :collection => true), Album.new, nil)
|
87
39
|
end
|
88
40
|
|
89
41
|
it "extracts with #read" do
|
@@ -104,7 +56,7 @@ class HashBindingTest < MiniTest::Spec
|
|
104
56
|
describe "HashBinding" do
|
105
57
|
describe "with plain text items" do
|
106
58
|
before do
|
107
|
-
@property = Representable::Hash::
|
59
|
+
@property = Representable::Hash::Binding::Hash.new(Representable::Definition.new(:songs, :hash => true), nil, nil)
|
108
60
|
end
|
109
61
|
|
110
62
|
it "extracts with #read" do
|
@@ -120,7 +72,7 @@ class HashBindingTest < MiniTest::Spec
|
|
120
72
|
|
121
73
|
describe "with objects" do
|
122
74
|
before do
|
123
|
-
@property = Representable::Hash::
|
75
|
+
@property = Representable::Hash::Binding::Hash.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter), nil, nil)
|
124
76
|
end
|
125
77
|
|
126
78
|
it "doesn't change the represented hash in #write" do
|
data/test/hash_test.rb
CHANGED
@@ -14,7 +14,7 @@ class HashTest < MiniTest::Spec
|
|
14
14
|
|
15
15
|
|
16
16
|
describe "property" do
|
17
|
-
let (:
|
17
|
+
let (:hsh) { hash_representer do property :best_song end }
|
18
18
|
|
19
19
|
let (:album) { Album.new.tap do |album|
|
20
20
|
album.best_song = "Liar"
|
@@ -22,14 +22,14 @@ class HashTest < MiniTest::Spec
|
|
22
22
|
|
23
23
|
describe "#to_hash" do
|
24
24
|
it "renders plain property" do
|
25
|
-
album.extend(
|
25
|
+
album.extend(hsh).to_hash.must_equal("best_song" => "Liar")
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
29
|
|
30
30
|
describe "#from_hash" do
|
31
31
|
it "parses plain property" do
|
32
|
-
album.extend(
|
32
|
+
album.extend(hsh).from_hash("best_song" => "This Song Is Recycled").best_song.must_equal "This Song Is Recycled"
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -95,7 +95,7 @@ class HashTest < MiniTest::Spec
|
|
95
95
|
|
96
96
|
|
97
97
|
describe "collection" do
|
98
|
-
let (:
|
98
|
+
let (:hsh) { hash_representer do collection :songs end }
|
99
99
|
|
100
100
|
let (:album) { Album.new.tap do |album|
|
101
101
|
album.songs = ["Jackhammer", "Terrible Man"]
|
@@ -104,14 +104,14 @@ class HashTest < MiniTest::Spec
|
|
104
104
|
|
105
105
|
describe "#to_hash" do
|
106
106
|
it "renders a block style list per default" do
|
107
|
-
album.extend(
|
107
|
+
album.extend(hsh).to_hash.must_equal("songs" => ["Jackhammer", "Terrible Man"])
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
111
|
|
112
112
|
describe "#from_hash" do
|
113
113
|
it "parses a block style list" do
|
114
|
-
album.extend(
|
114
|
+
album.extend(hsh).from_hash("songs" => ["Off Key Melody", "Sinking"]).must_equal Album.new(["Off Key Melody", "Sinking"])
|
115
115
|
|
116
116
|
end
|
117
117
|
end
|
data/test/json_test.rb
CHANGED
@@ -88,21 +88,21 @@ module JsonTest
|
|
88
88
|
|
89
89
|
describe "#build_for" do
|
90
90
|
it "returns TextBinding" do
|
91
|
-
assert_kind_of Representable::Hash::
|
91
|
+
assert_kind_of Representable::Hash::Binding, Representable::Hash::Binding.build_for(Def.new(:band), nil, nil)
|
92
92
|
end
|
93
93
|
|
94
94
|
it "returns HashBinding" do
|
95
|
-
assert_kind_of Representable::Hash::
|
95
|
+
assert_kind_of Representable::Hash::Binding::Hash, Representable::Hash::Binding.build_for(Def.new(:band, :hash => true), nil, nil)
|
96
96
|
end
|
97
97
|
|
98
98
|
it "returns CollectionBinding" do
|
99
|
-
assert_kind_of Representable::Hash::
|
99
|
+
assert_kind_of Representable::Hash::Binding::Collection, Representable::Hash::Binding.build_for(Def.new(:band, :collection => true), nil, nil)
|
100
100
|
end
|
101
101
|
end
|
102
102
|
|
103
103
|
# describe "#representable_bindings_for" do
|
104
104
|
# it "returns bindings for each property" do
|
105
|
-
# bins = @band.send(:representable_bindings_for, Representable::JSON::
|
105
|
+
# bins = @band.send(:representable_bindings_for, Representable::JSON::Binding, {})
|
106
106
|
# assert_equal 2, bins.size
|
107
107
|
# assert_equal "name", bins.first.name
|
108
108
|
# end
|
@@ -395,15 +395,15 @@ end
|
|
395
395
|
|
396
396
|
describe "parsing" do
|
397
397
|
subject { OpenStruct.new.extend(representer) }
|
398
|
-
let (:
|
398
|
+
let (:hsh) { {"7"=>{"name"=>"Contemplation"}} }
|
399
399
|
|
400
400
|
it "parses incoming hash" do
|
401
|
-
subject.from_hash("songs"=>
|
401
|
+
subject.from_hash("songs"=>hsh).songs.must_equal({"7"=>Song.new("Contemplation")})
|
402
402
|
end
|
403
403
|
|
404
404
|
it "doesn't modify the incoming hash" do
|
405
|
-
subject.from_hash("songs"=> incoming_hash =
|
406
|
-
|
405
|
+
subject.from_hash("songs"=> incoming_hash = hsh.dup)
|
406
|
+
hsh.must_equal incoming_hash
|
407
407
|
end
|
408
408
|
end
|
409
409
|
end
|