representable 0.11.0 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.textile +7 -0
- data/README.rdoc +60 -36
- data/lib/representable.rb +46 -23
- data/lib/representable/binding.rb +47 -0
- data/lib/representable/bindings/json_bindings.rb +19 -21
- data/lib/representable/bindings/xml_bindings.rb +13 -18
- data/lib/representable/definition.rb +8 -7
- data/lib/representable/json.rb +12 -11
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +10 -10
- data/test/definition_test.rb +10 -3
- data/test/json_test.rb +88 -52
- data/test/representable_test.rb +55 -46
- data/test/test_helper.rb +13 -0
- data/test/xml_test.rb +103 -55
- metadata +4 -3
@@ -1,12 +1,8 @@
|
|
1
|
+
require 'representable/binding'
|
2
|
+
|
1
3
|
module Representable
|
2
4
|
module XML
|
3
|
-
class Binding
|
4
|
-
attr_reader :definition
|
5
|
-
|
6
|
-
def initialize(definition)
|
7
|
-
@definition = definition
|
8
|
-
end
|
9
|
-
|
5
|
+
class Binding < Representable::Binding
|
10
6
|
def read(xml)
|
11
7
|
value_from_node(xml)
|
12
8
|
end
|
@@ -66,12 +62,17 @@ module Representable
|
|
66
62
|
|
67
63
|
# Represents a tag with object binding.
|
68
64
|
class ObjectBinding < Binding
|
65
|
+
include Representable::Binding::Hooks # includes #create_object and #write_object.
|
66
|
+
include Representable::Binding::Extend
|
67
|
+
|
69
68
|
# Adds the ref's markup to +xml+.
|
70
|
-
def write(xml,
|
69
|
+
def write(xml, object)
|
71
70
|
if definition.array?
|
72
|
-
|
71
|
+
object.each do |item|
|
72
|
+
write_entity(xml, item)
|
73
|
+
end
|
73
74
|
else
|
74
|
-
write_entity(xml,
|
75
|
+
write_entity(xml, object)
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
@@ -79,18 +80,12 @@ module Representable
|
|
79
80
|
# Deserializes the ref's element from +xml+.
|
80
81
|
def value_from_node(xml)
|
81
82
|
collect_for(xml) do |node|
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def write_collection(xml, collection)
|
87
|
-
collection.each do |item|
|
88
|
-
write_entity(xml, item)
|
83
|
+
create_object.from_node(node)
|
89
84
|
end
|
90
85
|
end
|
91
86
|
|
92
87
|
def write_entity(xml, entity)
|
93
|
-
xml.add_child(entity.to_node)
|
88
|
+
xml.add_child(write_object(entity).to_node)
|
94
89
|
end
|
95
90
|
end
|
96
91
|
end
|
@@ -1,16 +1,17 @@
|
|
1
1
|
module Representable
|
2
2
|
# Created at class compile time. Keeps configuration options for one property.
|
3
3
|
class Definition
|
4
|
-
attr_reader :name, :sought_type, :from, :default
|
4
|
+
attr_reader :name, :sought_type, :from, :default, :representer_module
|
5
5
|
alias_method :getter, :name
|
6
6
|
|
7
7
|
def initialize(sym, options={})
|
8
|
-
@name
|
9
|
-
@array
|
10
|
-
@from
|
11
|
-
@sought_type
|
12
|
-
@default
|
13
|
-
@default
|
8
|
+
@name = sym.to_s
|
9
|
+
@array = options[:collection]
|
10
|
+
@from = (options[:from] || name).to_s
|
11
|
+
@sought_type = options[:class] || options[:class] || :text # TODO: deprecate :class in 1.0.
|
12
|
+
@default = options[:default]
|
13
|
+
@default ||= [] if array?
|
14
|
+
@representer_module = options[:extend] # DISCUSS: move to Representable::DCI?
|
14
15
|
|
15
16
|
# FIXME: move me to xml.
|
16
17
|
if options[:from].to_s =~ /^@/
|
data/lib/representable/json.rb
CHANGED
@@ -15,7 +15,7 @@ module Representable
|
|
15
15
|
def self.included(base)
|
16
16
|
base.class_eval do
|
17
17
|
include Representable # either in Hero or HeroRepresentation.
|
18
|
-
extend ClassMethods
|
18
|
+
extend ClassMethods # DISCUSS: do that only for classes?
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -23,30 +23,31 @@ module Representable
|
|
23
23
|
module ClassMethods
|
24
24
|
# Creates a new object from the passed JSON document.
|
25
25
|
def from_json(*args, &block)
|
26
|
-
|
26
|
+
create_represented(*args, &block).from_json(*args)
|
27
27
|
end
|
28
28
|
|
29
29
|
def from_hash(*args, &block)
|
30
|
-
|
30
|
+
create_represented(*args, &block).from_hash(*args)
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
+
|
34
35
|
# Parses the body as JSON and delegates to #from_hash.
|
35
|
-
def from_json(data, *args
|
36
|
+
def from_json(data, *args)
|
36
37
|
data = ::JSON[data]
|
37
|
-
from_hash(data, *args
|
38
|
+
from_hash(data, *args)
|
38
39
|
end
|
39
40
|
|
40
|
-
def from_hash(data, options={}
|
41
|
+
def from_hash(data, options={})
|
41
42
|
if wrap = options[:wrap] || representation_wrap
|
42
43
|
data = data[wrap.to_s]
|
43
44
|
end
|
44
45
|
|
45
|
-
update_properties_from(data,
|
46
|
+
update_properties_from(data, options)
|
46
47
|
end
|
47
48
|
|
48
|
-
def to_hash(options={}
|
49
|
-
hash = create_representation_with({},
|
49
|
+
def to_hash(options={})
|
50
|
+
hash = create_representation_with({}, options)
|
50
51
|
|
51
52
|
return hash unless wrap = options[:wrap] || representation_wrap
|
52
53
|
|
@@ -54,8 +55,8 @@ module Representable
|
|
54
55
|
end
|
55
56
|
|
56
57
|
# Returns a JSON string representing this object.
|
57
|
-
def to_json(*args
|
58
|
-
to_hash(*args
|
58
|
+
def to_json(*args)
|
59
|
+
to_hash(*args).to_json
|
59
60
|
end
|
60
61
|
|
61
62
|
def binding_for_definition(definition)
|
data/lib/representable/xml.rb
CHANGED
@@ -27,33 +27,33 @@ module Representable
|
|
27
27
|
# Example:
|
28
28
|
# band.from_xml("<band><name>Nofx</name></band>")
|
29
29
|
def from_xml(*args, &block)
|
30
|
-
|
30
|
+
create_represented(*args, &block).from_xml(*args)
|
31
31
|
end
|
32
32
|
|
33
33
|
def from_node(*args, &block)
|
34
|
-
|
34
|
+
create_represented(*args, &block).from_node(*args)
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
38
|
|
39
|
-
def from_xml(doc, *args
|
39
|
+
def from_xml(doc, *args)
|
40
40
|
node = Nokogiri::XML(doc).root
|
41
|
-
from_node(node, *args
|
41
|
+
from_node(node, *args)
|
42
42
|
end
|
43
43
|
|
44
|
-
def from_node(node, options={}
|
45
|
-
update_properties_from(node,
|
44
|
+
def from_node(node, options={})
|
45
|
+
update_properties_from(node, options)
|
46
46
|
end
|
47
47
|
|
48
48
|
# Returns a Nokogiri::XML object representing this object.
|
49
|
-
def to_node(options={}
|
49
|
+
def to_node(options={})
|
50
50
|
root_tag = options[:wrap] || representation_wrap
|
51
51
|
|
52
|
-
create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, Nokogiri::XML::Document.new),
|
52
|
+
create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, Nokogiri::XML::Document.new), options)
|
53
53
|
end
|
54
54
|
|
55
|
-
def to_xml(*args
|
56
|
-
to_node(*args
|
55
|
+
def to_xml(*args)
|
56
|
+
to_node(*args).to_s
|
57
57
|
end
|
58
58
|
|
59
59
|
def binding_for_definition(definition)
|
data/test/definition_test.rb
CHANGED
@@ -6,9 +6,16 @@ class DefinitionTest < MiniTest::Spec
|
|
6
6
|
@def = Representable::Definition.new(:songs)
|
7
7
|
end
|
8
8
|
|
9
|
+
describe "DCI" do
|
10
|
+
it "responds to #representer_module" do
|
11
|
+
assert_equal nil, Representable::Definition.new(:song).representer_module
|
12
|
+
assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
9
16
|
it "responds to #typed?" do
|
10
17
|
assert ! @def.typed?
|
11
|
-
assert Representable::Definition.new(:songs, :
|
18
|
+
assert Representable::Definition.new(:songs, :class => Hash).typed?
|
12
19
|
end
|
13
20
|
|
14
21
|
it "responds to #getter and returns string" do
|
@@ -68,9 +75,9 @@ class DefinitionTest < MiniTest::Spec
|
|
68
75
|
end
|
69
76
|
end
|
70
77
|
|
71
|
-
describe ":
|
78
|
+
describe ":class => Item" do
|
72
79
|
before do
|
73
|
-
@def = Representable::Definition.new(:songs, :
|
80
|
+
@def = Representable::Definition.new(:songs, :class => Hash)
|
74
81
|
end
|
75
82
|
|
76
83
|
it "responds to #sought_type" do
|
data/test/json_test.rb
CHANGED
@@ -10,8 +10,8 @@ module JsonTest
|
|
10
10
|
before do
|
11
11
|
@Band = Class.new do
|
12
12
|
include Representable::JSON
|
13
|
-
|
14
|
-
|
13
|
+
property :name
|
14
|
+
property :label
|
15
15
|
|
16
16
|
def initialize(name=nil)
|
17
17
|
self.name = name if name
|
@@ -24,18 +24,35 @@ module JsonTest
|
|
24
24
|
|
25
25
|
describe ".from_json" do
|
26
26
|
it "is delegated to #from_json" do
|
27
|
-
block = lambda {|
|
28
|
-
@Band.any_instance.expects(:from_json).with("{}", "
|
29
|
-
@Band.from_json("{}", "
|
27
|
+
block = lambda {|*args|}
|
28
|
+
@Band.any_instance.expects(:from_json).with("{document}", "options") # FIXME: how to NOT expect block?
|
29
|
+
@Band.from_json("{document}", "options", &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "yields new object and options to block" do
|
33
|
+
@Band.class_eval { attr_accessor :new_name }
|
34
|
+
@band = @Band.from_json({}, :new_name => "Diesel Boy") do |band, options|
|
35
|
+
band.new_name= options[:new_name]
|
36
|
+
end
|
37
|
+
assert_equal "Diesel Boy", @band.new_name
|
30
38
|
end
|
31
39
|
end
|
32
40
|
|
33
41
|
|
34
42
|
describe ".from_hash" do
|
35
|
-
it "is delegated to #from_hash" do
|
36
|
-
block = lambda {|
|
37
|
-
@Band.any_instance.expects(:from_hash).with("{}", "
|
38
|
-
@Band.from_hash("{}", "
|
43
|
+
it "is delegated to #from_hash not passing the block" do
|
44
|
+
block = lambda {|*args|}
|
45
|
+
@Band.any_instance.expects(:from_hash).with("{document}", "options") # FIXME: how to NOT expect block?
|
46
|
+
@Band.from_hash("{document}", "options", &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "yields new object and options to block" do
|
50
|
+
@Band.class_eval { attr_accessor :new_name }
|
51
|
+
@band = @Band.from_hash({}, :new_name => "Diesel Boy") do |band, options|
|
52
|
+
band.new_name= options[:new_name]
|
53
|
+
end
|
54
|
+
|
55
|
+
assert_equal "Diesel Boy", @band.new_name
|
39
56
|
end
|
40
57
|
end
|
41
58
|
|
@@ -50,14 +67,6 @@ module JsonTest
|
|
50
67
|
@band.from_json(@json)
|
51
68
|
assert_equal ["Nofx", "NOFX"], [@band.name, @band.label]
|
52
69
|
end
|
53
|
-
|
54
|
-
it "forwards block to #from_hash" do
|
55
|
-
@band.from_json(@json) do |name|
|
56
|
-
name == :name
|
57
|
-
end
|
58
|
-
|
59
|
-
assert_equal ["Nofx", nil], [@band.name, @band.label]
|
60
|
-
end
|
61
70
|
end
|
62
71
|
|
63
72
|
|
@@ -72,14 +81,6 @@ module JsonTest
|
|
72
81
|
assert_equal ["Nofx", "NOFX"], [@band.name, @band.label]
|
73
82
|
end
|
74
83
|
|
75
|
-
it "forwards block to #update_properties_from" do
|
76
|
-
@band.from_hash(@hash) do |name|
|
77
|
-
name == :name
|
78
|
-
end
|
79
|
-
|
80
|
-
assert_equal ["Nofx", nil], [@band.name, @band.label]
|
81
|
-
end
|
82
|
-
|
83
84
|
it "respects :wrap option" do
|
84
85
|
@band.from_hash({"band" => {"name" => "This Is A Standoff"}}, :wrap => :band)
|
85
86
|
assert_equal "This Is A Standoff", @band.name
|
@@ -99,17 +100,6 @@ module JsonTest
|
|
99
100
|
it "delegates to #to_hash and returns string" do
|
100
101
|
assert_equal "{\"name\":\"Rise Against\"}", @Band.new("Rise Against").to_json
|
101
102
|
end
|
102
|
-
|
103
|
-
it "forwards block to #to_hash" do
|
104
|
-
band = @Band.new
|
105
|
-
band.name = "The Guinea Pigs"
|
106
|
-
band.label = "n/a"
|
107
|
-
json = band.to_json do |name|
|
108
|
-
name == :name
|
109
|
-
end
|
110
|
-
|
111
|
-
assert_equal "{\"name\":\"The Guinea Pigs\"}", json
|
112
|
-
end
|
113
103
|
end
|
114
104
|
|
115
105
|
|
@@ -138,7 +128,7 @@ module JsonTest
|
|
138
128
|
|
139
129
|
describe "#binding_for_definition" do
|
140
130
|
it "returns ObjectBinding" do
|
141
|
-
assert_kind_of Json::ObjectBinding, @band.binding_for_definition(Def.new(:band, :
|
131
|
+
assert_kind_of Json::ObjectBinding, @band.binding_for_definition(Def.new(:band, :class => Hash))
|
142
132
|
end
|
143
133
|
|
144
134
|
it "returns TextBinding" do
|
@@ -153,13 +143,59 @@ module JsonTest
|
|
153
143
|
end
|
154
144
|
end
|
155
145
|
end
|
146
|
+
|
147
|
+
|
148
|
+
describe "DCI" do
|
149
|
+
module SongRepresenter
|
150
|
+
include Representable::JSON
|
151
|
+
property :name
|
152
|
+
end
|
153
|
+
|
154
|
+
module AlbumRepresenter
|
155
|
+
include Representable::JSON
|
156
|
+
property :best_song, :class => Song, :extend => SongRepresenter
|
157
|
+
collection :songs, :class => Song, :extend => SongRepresenter
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
it "allows adding the representer by using #extend" do
|
162
|
+
module BandRepresenter
|
163
|
+
include Representable::JSON
|
164
|
+
property :name
|
165
|
+
end
|
166
|
+
|
167
|
+
civ = Object.new
|
168
|
+
civ.instance_eval do
|
169
|
+
def name; "CIV"; end
|
170
|
+
end
|
171
|
+
|
172
|
+
civ.extend(BandRepresenter)
|
173
|
+
assert_equal "{\"name\":\"CIV\"}", civ.to_json
|
174
|
+
end
|
175
|
+
|
176
|
+
it "extends contained models when serializing" do
|
177
|
+
@album = Album.new(Song.new("I Hate My Brain"), Song.new("Mr. Charisma"))
|
178
|
+
@album.extend(AlbumRepresenter)
|
179
|
+
|
180
|
+
assert_equal "{\"best_song\":{\"name\":\"Mr. Charisma\"},\"songs\":[{\"name\":\"I Hate My Brain\"},{\"name\":\"Mr. Charisma\"}]}", @album.to_json
|
181
|
+
end
|
182
|
+
|
183
|
+
it "extends contained models when deserializing" do
|
184
|
+
#@album = Album.new(Song.new("I Hate My Brain"), Song.new("Mr. Charisma"))
|
185
|
+
@album = Album.new
|
186
|
+
@album.extend(AlbumRepresenter)
|
187
|
+
|
188
|
+
@album.from_json("{\"best_song\":{\"name\":\"Mr. Charisma\"},\"songs\":[{\"name\":\"I Hate My Brain\"},{\"name\":\"Mr. Charisma\"}]}")
|
189
|
+
assert_equal "Mr. Charisma", @album.best_song.name
|
190
|
+
end
|
191
|
+
end
|
156
192
|
end
|
157
193
|
|
158
194
|
class PropertyTest < MiniTest::Spec
|
159
|
-
describe "
|
195
|
+
describe "property :name" do
|
160
196
|
class Band
|
161
197
|
include Representable::JSON
|
162
|
-
|
198
|
+
property :name
|
163
199
|
end
|
164
200
|
|
165
201
|
it "#from_json creates correct accessors" do
|
@@ -175,15 +211,15 @@ module JsonTest
|
|
175
211
|
end
|
176
212
|
end
|
177
213
|
|
178
|
-
describe ":
|
214
|
+
describe ":class => Item" do
|
179
215
|
class Label
|
180
216
|
include Representable::JSON
|
181
|
-
|
217
|
+
property :name
|
182
218
|
end
|
183
219
|
|
184
220
|
class Album
|
185
221
|
include Representable::JSON
|
186
|
-
|
222
|
+
property :label, :class => Label
|
187
223
|
end
|
188
224
|
|
189
225
|
it "#from_json creates one Item instance" do
|
@@ -198,11 +234,11 @@ module JsonTest
|
|
198
234
|
assert_equal '{"label":{"name":"Fat Wreck"}}', album.to_json
|
199
235
|
end
|
200
236
|
|
201
|
-
describe ":different_name, :
|
237
|
+
describe ":different_name, :class => Label" do
|
202
238
|
before do
|
203
239
|
@Album = Class.new do
|
204
240
|
include Representable::JSON
|
205
|
-
|
241
|
+
property :seller, :class => Label
|
206
242
|
end
|
207
243
|
end
|
208
244
|
|
@@ -218,7 +254,7 @@ module JsonTest
|
|
218
254
|
describe ":from => :songName" do
|
219
255
|
class Song
|
220
256
|
include Representable::JSON
|
221
|
-
|
257
|
+
property :name, :from => :songName
|
222
258
|
end
|
223
259
|
|
224
260
|
it "respects :from in #from_json" do
|
@@ -236,7 +272,7 @@ module JsonTest
|
|
236
272
|
before do
|
237
273
|
@Album = Class.new do
|
238
274
|
include Representable::JSON
|
239
|
-
|
275
|
+
property :name, :default => "30 Years Live"
|
240
276
|
end
|
241
277
|
end
|
242
278
|
|
@@ -279,10 +315,10 @@ end
|
|
279
315
|
|
280
316
|
|
281
317
|
class CollectionTest < MiniTest::Spec
|
282
|
-
describe "
|
318
|
+
describe "collection :name" do
|
283
319
|
class CD
|
284
320
|
include Representable::JSON
|
285
|
-
|
321
|
+
collection :songs
|
286
322
|
end
|
287
323
|
|
288
324
|
it "#from_json creates correct accessors" do
|
@@ -298,10 +334,10 @@ end
|
|
298
334
|
end
|
299
335
|
end
|
300
336
|
|
301
|
-
describe "
|
337
|
+
describe "collection :name, :class => Band" do
|
302
338
|
class Band
|
303
339
|
include Representable::JSON
|
304
|
-
|
340
|
+
property :name
|
305
341
|
|
306
342
|
def initialize(name="")
|
307
343
|
self.name = name
|
@@ -310,7 +346,7 @@ end
|
|
310
346
|
|
311
347
|
class Compilation
|
312
348
|
include Representable::JSON
|
313
|
-
|
349
|
+
collection :bands, :class => Band
|
314
350
|
end
|
315
351
|
|
316
352
|
describe "#from_json" do
|
@@ -339,7 +375,7 @@ end
|
|
339
375
|
describe ":from => :songList" do
|
340
376
|
class Songs
|
341
377
|
include Representable::JSON
|
342
|
-
|
378
|
+
collection :tracks, :from => :songList
|
343
379
|
end
|
344
380
|
|
345
381
|
it "respects :from in #from_json" do
|