representable 2.0.4 → 2.1.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 +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
data/test/lonely_test.rb
CHANGED
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'benchmark'
|
3
|
+
|
4
|
+
SONG_PROPERTIES = 50.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: 50.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
|
data/test/skip_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class SkipTest < MiniTest::Spec
|
4
|
+
representer! do
|
5
|
+
property :title
|
6
|
+
property :band,
|
7
|
+
skip_parse: lambda { |fragment, opts| opts[:skip?] and fragment["name"].nil? }, class: OpenStruct do
|
8
|
+
property :name
|
9
|
+
end
|
10
|
+
|
11
|
+
collection :airplays,
|
12
|
+
skip_parse: lambda { |fragment, opts| puts fragment.inspect; opts[:skip?] and fragment["station"].nil? }, class: OpenStruct do
|
13
|
+
property :station
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let (:song) { OpenStruct.new.extend(representer) }
|
18
|
+
|
19
|
+
# do parse.
|
20
|
+
it { song.from_hash({"band" => {"name" => "Mute 98"}}, skip?: true).band.name.must_equal "Mute 98" }
|
21
|
+
it { song.from_hash({"airplays" => [{"station" => "JJJ"}]}, skip?: true).airplays[0].station.must_equal "JJJ" }
|
22
|
+
|
23
|
+
# skip parsing.
|
24
|
+
it { song.from_hash({"band" => {}}, skip?: true).band.must_equal nil }
|
25
|
+
# skip_parse is _per item_.
|
26
|
+
let (:airplay) { OpenStruct.new(station: "JJJ") }
|
27
|
+
it { song.from_hash({"airplays" => [{"station" => "JJJ"}, {}]}, skip?: true).airplays.must_equal [airplay] }
|
28
|
+
end
|
data/test/xml_bindings_test.rb
CHANGED
@@ -23,120 +23,13 @@ class XMLBindingTest < MiniTest::Spec
|
|
23
23
|
@song = SongWithRepresenter.new("Thinning the Herd")
|
24
24
|
end
|
25
25
|
|
26
|
-
describe "PropertyBinding" do
|
27
|
-
describe "with plain text" do
|
28
|
-
before do
|
29
|
-
@property = Representable::XML::PropertyBinding.new(Representable::Definition.new(:song), nil, nil, {:doc => @doc})
|
30
|
-
end
|
31
|
-
|
32
|
-
it "extracts with #read" do
|
33
|
-
assert_equal "Thinning the Herd", @property.read(Nokogiri::XML("<song>Thinning the Herd</song>"))
|
34
|
-
end
|
35
|
-
|
36
|
-
it "inserts with #write" do
|
37
|
-
@property.write(@doc, "Thinning the Herd")
|
38
|
-
assert_xml_equal "<song>Thinning the Herd</song>", @doc.to_s
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe "with an object" do
|
43
|
-
before do
|
44
|
-
@property = Representable::XML::PropertyBinding.new(Representable::Definition.new(:song, :class => SongWithRepresenter), nil, nil, {:doc => @doc})
|
45
|
-
end
|
46
|
-
|
47
|
-
it "extracts with #read" do
|
48
|
-
assert_equal @song, @property.read(Nokogiri::XML("<song><name>Thinning the Herd</name></song>"))
|
49
|
-
end
|
50
|
-
|
51
|
-
it "inserts with #write" do
|
52
|
-
@property.write(@doc, @song)
|
53
|
-
assert_xml_equal("<song><name>Thinning the Herd</name></song>", @doc.to_s)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
describe "with an object and :extend" do
|
58
|
-
before do
|
59
|
-
@property = Representable::XML::PropertyBinding.new(Representable::Definition.new(:song, :class => Song, :extend => SongRepresenter), nil, nil, {:doc => @doc})
|
60
|
-
end
|
61
|
-
|
62
|
-
it "extracts with #read" do
|
63
|
-
assert_equal @song, @property.read(Nokogiri::XML("<song><name>Thinning the Herd</name></song>"))
|
64
|
-
end
|
65
|
-
|
66
|
-
it "inserts with #write" do
|
67
|
-
@property.write(@doc, @song)
|
68
|
-
assert_xml_equal("<song><name>Thinning the Herd</name></song>", @doc.to_s)
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
describe "CollectionBinding" do
|
75
|
-
describe "with plain text items" do
|
76
|
-
before do
|
77
|
-
@property = Representable::XML::CollectionBinding.new(Representable::Definition.new(:song, :collection => true), Struct.new(:song).new, nil)
|
78
|
-
end
|
79
26
|
|
80
|
-
it "extracts with #read" do
|
81
|
-
assert_equal ["The Gargoyle", "Bronx"], @property.read(Nokogiri::XML("<doc><song>The Gargoyle</song><song>Bronx</song></doc>").root)
|
82
|
-
end
|
83
|
-
|
84
|
-
it "inserts with #write" do
|
85
|
-
parent = Nokogiri::XML::Node.new("parent", @doc)
|
86
|
-
@property.write(parent, ["The Gargoyle", "Bronx"])
|
87
|
-
assert_xml_equal("<songs><song>The Gargoyle</song><song>Bronx</song></songs>", parent.to_s)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
describe "with objects" do
|
92
|
-
before do
|
93
|
-
@property = Representable::XML::PropertyBinding.new(Representable::Definition.new(:song, :collection => true, :class => SongWithRepresenter), nil, nil, {:doc => @doc})
|
94
|
-
end
|
95
|
-
|
96
|
-
it "extracts with #read" do
|
97
|
-
assert_equal @song, @property.read(Nokogiri::XML("<song><name>Thinning the Herd</name></song>"))
|
98
|
-
end
|
99
|
-
|
100
|
-
it "inserts with #write" do
|
101
|
-
@property.write(@doc, @song)
|
102
|
-
assert_xml_equal("<song><name>Thinning the Herd</name></song>", @doc.to_s)
|
103
|
-
assert_kind_of Nokogiri::XML::Node, @doc.children.first
|
104
|
-
assert_equal "song", @doc.children.first.name
|
105
|
-
assert_equal "name", @doc.children.first.children.first.name
|
106
|
-
end
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
|
111
|
-
describe "HashBinding" do
|
112
|
-
describe "with plain text items" do
|
113
|
-
before do
|
114
|
-
@property = Representable::XML::HashBinding.new(Representable::Definition.new(:songs, :hash => true), nil, nil)
|
115
|
-
end
|
116
|
-
|
117
|
-
it "extracts with #read" do
|
118
|
-
assert_equal({"first" => "The Gargoyle", "second" => "Bronx"} , @property.read(Nokogiri::XML("<songs><first>The Gargoyle</first><second>Bronx</second></songs>")))
|
119
|
-
end
|
120
|
-
|
121
|
-
it "inserts with #write" do
|
122
|
-
parent = Nokogiri::XML::Node.new("parent", @doc)
|
123
|
-
@property.write(parent, {"first" => "The Gargoyle", "second" => "Bronx"})
|
124
|
-
assert_xml_equal("<songs><first>The Gargoyle</first><second>Bronx</second></songs>", parent.to_s)
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
describe "with objects" do
|
129
|
-
before do
|
130
|
-
@property = Representable::XML::HashBinding.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter), nil)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
27
|
|
135
28
|
|
136
29
|
describe "AttributeBinding" do
|
137
30
|
describe "with plain text items" do
|
138
31
|
before do
|
139
|
-
@property = Representable::XML::
|
32
|
+
@property = Representable::XML::Binding::Attribute.new(Representable::Definition.new(:name, :attribute => true), nil, nil)
|
140
33
|
end
|
141
34
|
|
142
35
|
it "extracts with #read" do
|
@@ -153,7 +46,7 @@ class XMLBindingTest < MiniTest::Spec
|
|
153
46
|
|
154
47
|
describe "ContentBinding" do
|
155
48
|
before do
|
156
|
-
@property = Representable::XML::
|
49
|
+
@property = Representable::XML::Binding::Content.new(Representable::Definition.new(:name, :content => true), nil, nil)
|
157
50
|
end
|
158
51
|
|
159
52
|
it "extracts with #read" do
|
data/test/xml_test.rb
CHANGED
@@ -104,20 +104,20 @@ class XmlTest < MiniTest::Spec
|
|
104
104
|
|
105
105
|
describe "XML::Binding#build_for" do
|
106
106
|
it "returns AttributeBinding" do
|
107
|
-
assert_kind_of XML::
|
107
|
+
assert_kind_of XML::Binding::Attribute, XML::Binding.build_for(Def.new(:band, :as => "band", :attribute => true), nil, nil)
|
108
108
|
end
|
109
109
|
|
110
|
-
it "returns
|
111
|
-
assert_kind_of XML::
|
112
|
-
assert_kind_of XML::
|
110
|
+
it "returns Binding" do
|
111
|
+
assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :class => Hash), nil, nil)
|
112
|
+
assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :as => :content), nil, nil)
|
113
113
|
end
|
114
114
|
|
115
115
|
it "returns CollectionBinding" do
|
116
|
-
assert_kind_of XML::
|
116
|
+
assert_kind_of XML::Binding::Collection, XML::Binding.build_for(Def.new(:band, :collection => :true), nil, nil)
|
117
117
|
end
|
118
118
|
|
119
119
|
it "returns HashBinding" do
|
120
|
-
assert_kind_of XML::
|
120
|
+
assert_kind_of XML::Binding::Hash, XML::Binding.build_for(Def.new(:band, :hash => :true), nil, nil)
|
121
121
|
end
|
122
122
|
end
|
123
123
|
|
@@ -206,7 +206,7 @@ end
|
|
206
206
|
|
207
207
|
|
208
208
|
class CDataBand
|
209
|
-
class CData < Representable::XML::
|
209
|
+
class CData < Representable::XML::Binding
|
210
210
|
def serialize_node(parent, value)
|
211
211
|
parent << Nokogiri::XML::CDATA.new(parent, represented.name)
|
212
212
|
end
|
@@ -320,14 +320,14 @@ class CollectionTest < MiniTest::Spec
|
|
320
320
|
|
321
321
|
|
322
322
|
describe ":as" do
|
323
|
-
let(:
|
323
|
+
let(:xml_doc) {
|
324
324
|
Module.new do
|
325
325
|
include Representable::XML
|
326
326
|
collection :songs, :as => :song
|
327
327
|
end }
|
328
328
|
|
329
329
|
it "collects untyped items" do
|
330
|
-
album = Album.new.extend(
|
330
|
+
album = Album.new.extend(xml_doc).from_xml(%{
|
331
331
|
<album>
|
332
332
|
<song>Two Kevins</song>
|
333
333
|
<song>Wright and Rong</song>
|
@@ -340,8 +340,8 @@ class CollectionTest < MiniTest::Spec
|
|
340
340
|
|
341
341
|
|
342
342
|
describe ":wrap" do
|
343
|
-
let (:album) { Album.new.extend(
|
344
|
-
let (:
|
343
|
+
let (:album) { Album.new.extend(xml_doc) }
|
344
|
+
let (:xml_doc) {
|
345
345
|
Module.new do
|
346
346
|
include Representable::XML
|
347
347
|
collection :songs, :as => :song, :wrap => :songs
|
@@ -397,22 +397,22 @@ class CollectionTest < MiniTest::Spec
|
|
397
397
|
end
|
398
398
|
|
399
399
|
let (:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] }
|
400
|
-
let (:
|
400
|
+
let (:xml_doc) { "<songs><song><name>Days Go By</name></song><song><name>Can't Take Them All</name></song></songs>" }
|
401
401
|
|
402
402
|
it "renders array" do
|
403
|
-
songs.extend(representer).to_xml.must_equal_xml
|
403
|
+
songs.extend(representer).to_xml.must_equal_xml xml_doc
|
404
404
|
end
|
405
405
|
|
406
406
|
it "renders array with decorator" do
|
407
|
-
decorator.new(songs).to_xml.must_equal_xml
|
407
|
+
decorator.new(songs).to_xml.must_equal_xml xml_doc
|
408
408
|
end
|
409
409
|
|
410
410
|
it "parses array" do
|
411
|
-
[].extend(representer).from_xml(
|
411
|
+
[].extend(representer).from_xml(xml_doc).must_equal songs
|
412
412
|
end
|
413
413
|
|
414
414
|
it "parses array with decorator" do
|
415
|
-
decorator.new([]).from_xml(
|
415
|
+
decorator.new([]).from_xml(xml_doc).must_equal songs
|
416
416
|
end
|
417
417
|
end
|
418
418
|
end
|
@@ -423,11 +423,11 @@ class CollectionTest < MiniTest::Spec
|
|
423
423
|
end
|
424
424
|
|
425
425
|
let (:songs) { {"one" => "Graveyards", "two" => "Can't Take Them All"} }
|
426
|
-
let (:
|
426
|
+
let (:xml_doc) { "<favs one=\"Graveyards\" two=\"Can't Take Them All\" />" }
|
427
427
|
|
428
428
|
describe "#to_xml" do
|
429
429
|
it "renders hash" do
|
430
|
-
songs.extend(representer).to_xml.must_equal_xml
|
430
|
+
songs.extend(representer).to_xml.must_equal_xml xml_doc
|
431
431
|
end
|
432
432
|
|
433
433
|
it "respects :exclude" do
|
@@ -439,27 +439,65 @@ class CollectionTest < MiniTest::Spec
|
|
439
439
|
end
|
440
440
|
|
441
441
|
it "renders hash with decorator" do
|
442
|
-
decorator.new(songs).to_xml.must_equal_xml
|
442
|
+
decorator.new(songs).to_xml.must_equal_xml xml_doc
|
443
443
|
end
|
444
444
|
end
|
445
445
|
|
446
446
|
describe "#from_json" do
|
447
447
|
it "returns hash" do
|
448
|
-
{}.extend(representer).from_xml(
|
448
|
+
{}.extend(representer).from_xml(xml_doc).must_equal songs
|
449
449
|
end
|
450
450
|
|
451
451
|
it "respects :exclude" do
|
452
|
-
assert_equal({"two" => "Can't Take Them All"}, {}.extend(representer).from_xml(
|
452
|
+
assert_equal({"two" => "Can't Take Them All"}, {}.extend(representer).from_xml(xml_doc, :exclude => [:one]))
|
453
453
|
end
|
454
454
|
|
455
455
|
it "respects :include" do
|
456
|
-
assert_equal({"one" => "Graveyards"}, {}.extend(representer).from_xml(
|
456
|
+
assert_equal({"one" => "Graveyards"}, {}.extend(representer).from_xml(xml_doc, :include => [:one]))
|
457
457
|
end
|
458
458
|
|
459
459
|
it "parses hash with decorator" do
|
460
|
-
decorator.new({}).from_xml(
|
460
|
+
decorator.new({}).from_xml(xml_doc).must_equal songs
|
461
461
|
end
|
462
462
|
end
|
463
463
|
end
|
464
464
|
end
|
465
465
|
end
|
466
|
+
|
467
|
+
class XmlHashTest < MiniTest::Spec
|
468
|
+
# scalar, no object
|
469
|
+
describe "plain text" do
|
470
|
+
representer!(module: Representable::XML) do
|
471
|
+
hash :songs
|
472
|
+
end
|
473
|
+
|
474
|
+
let (:doc) { "<open_struct><first>The Gargoyle</first><second>Bronx</second></open_struct>" }
|
475
|
+
|
476
|
+
# to_xml
|
477
|
+
it { OpenStruct.new(songs: {"first" => "The Gargoyle", "second" => "Bronx"}).extend(representer).to_xml.must_equal_xml(doc) }
|
478
|
+
# FIXME: this NEVER worked!
|
479
|
+
# it { OpenStruct.new.extend(representer).from_xml(doc).songs.must_equal({"first" => "The Gargoyle", "second" => "Bronx"}) }
|
480
|
+
end
|
481
|
+
|
482
|
+
describe "with objects" do
|
483
|
+
representer!(module: Representable::XML) do
|
484
|
+
hash :songs, class: OpenStruct do
|
485
|
+
property :title
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
let (:doc) { "<open_struct>
|
490
|
+
<open_struct>
|
491
|
+
<title>The Gargoyle</title>
|
492
|
+
</open_struct>
|
493
|
+
<open_struct>
|
494
|
+
<title>Bronx</title>
|
495
|
+
</open_struct>
|
496
|
+
</open_struct>" }
|
497
|
+
|
498
|
+
# to_xml
|
499
|
+
it { OpenStruct.new(songs: {"first" => OpenStruct.new(title: "The Gargoyle"), "second" => OpenStruct.new(title: "Bronx")}).extend(representer).to_xml.must_equal_xml(doc) }
|
500
|
+
# FIXME: this NEVER worked!
|
501
|
+
# it { OpenStruct.new.extend(representer).from_xml(doc).songs.must_equal({"first" => "The Gargoyle", "second" => "Bronx"}) }
|
502
|
+
end
|
503
|
+
end
|
data/test/yaml_test.rb
CHANGED
@@ -124,18 +124,15 @@ songs: [Off Key Melody, Sinking]").must_equal Album.new(["Off Key Melody", "Sink
|
|
124
124
|
|
125
125
|
|
126
126
|
describe "with :class and :extend" do
|
127
|
-
yaml_song = yaml_representer do
|
128
|
-
property :name
|
129
|
-
property :track
|
130
|
-
end
|
131
127
|
let (:yaml_album) { Module.new do
|
132
128
|
include Representable::YAML
|
133
|
-
collection :songs, :
|
129
|
+
collection :songs, :class => Song do
|
130
|
+
property :name
|
131
|
+
property :track
|
132
|
+
end
|
134
133
|
end }
|
135
134
|
|
136
|
-
let (:album) { Album.new.
|
137
|
-
album.songs = [Song.new("Liar", 1), Song.new("What I Know", 2)]
|
138
|
-
end }
|
135
|
+
let (:album) { Album.new([Song.new("Liar", 1), Song.new("What I Know", 2)]) }
|
139
136
|
|
140
137
|
|
141
138
|
describe "#to_yaml" do
|