representable 0.9.3 → 0.10.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 +11 -1
- data/lib/representable.rb +15 -8
- data/lib/representable/bindings/json_bindings.rb +2 -2
- data/lib/representable/bindings/xml_bindings.rb +16 -47
- data/lib/representable/definition.rb +3 -15
- data/lib/representable/json.rb +17 -23
- data/lib/representable/version.rb +1 -1
- data/lib/representable/xml.rb +18 -22
- data/test/bindings_test.rb +1 -1
- data/test/json_test.rb +86 -25
- data/test/representable_test.rb +16 -6
- data/test/xml_test.rb +107 -56
- metadata +4 -5
- data/lib/representable/nokogiri_extensions.rb +0 -19
data/CHANGES.textile
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
h2. 0.10.0
|
2
|
+
|
3
|
+
* Wrapping can now be set through @Representable.representation_wrap=@. Possible values are:
|
4
|
+
* @false@: No wrapping. In XML context, this is undefined behaviour. Default in JSON.
|
5
|
+
* @String@: Wrap with provided string.
|
6
|
+
* @true@: compute wrapper from class name.
|
7
|
+
|
1
8
|
h2. 0.9.3
|
2
9
|
|
3
10
|
* Removed the @:as => [..]@ syntax in favor of @:array => true@.
|
data/README.rdoc
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
= Representable
|
2
2
|
|
3
|
-
<em>Maps
|
3
|
+
<em>Maps documents to Ruby objects and back.</em>
|
4
4
|
|
5
5
|
|
6
6
|
== Introduction
|
@@ -9,8 +9,18 @@ _Representable_ maps fragments in documents to attributes in Ruby objects and ba
|
|
9
9
|
|
10
10
|
This is especially helpful when implementing REST services and clients and keeps your representation knowledge in one place.
|
11
11
|
|
12
|
+
|
13
|
+
== Features
|
14
|
+
|
15
|
+
* Bidirectional - rendering and parsing
|
16
|
+
* OOP documents
|
17
|
+
* Support for JSON and XML
|
18
|
+
|
19
|
+
|
12
20
|
== Example
|
13
21
|
|
22
|
+
|
23
|
+
|
14
24
|
When writing a REST service you'd have to parse and extract data from an incoming representation document manually. Given the following XML document which represents an order.
|
15
25
|
|
16
26
|
<order>
|
data/lib/representable.rb
CHANGED
@@ -1,18 +1,17 @@
|
|
1
1
|
require 'hooks/inheritable_attribute'
|
2
2
|
require 'representable/definition'
|
3
|
-
require 'representable/nokogiri_extensions'
|
4
|
-
|
5
3
|
|
6
4
|
module Representable
|
7
5
|
def self.included(base)
|
8
6
|
base.class_eval do
|
9
|
-
extend
|
7
|
+
extend ClassMethods::Declarations
|
8
|
+
extend ClassMethods::Accessors
|
10
9
|
|
11
10
|
extend Hooks::InheritableAttribute
|
12
11
|
inheritable_attr :representable_attrs
|
13
12
|
self.representable_attrs = []
|
14
13
|
|
15
|
-
inheritable_attr :
|
14
|
+
inheritable_attr :representable_wrap
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
@@ -85,12 +84,20 @@ private
|
|
85
84
|
end
|
86
85
|
|
87
86
|
module Accessors
|
88
|
-
def
|
89
|
-
self.
|
87
|
+
def representation_wrap=(name)
|
88
|
+
self.representable_wrap = name
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns the wrapper for the representation. Mostly used in XML.
|
92
|
+
def representation_wrap
|
93
|
+
return unless representable_wrap
|
94
|
+
return infer_representation_name if representable_wrap === true
|
95
|
+
representable_wrap
|
90
96
|
end
|
91
97
|
|
92
|
-
|
93
|
-
|
98
|
+
private
|
99
|
+
def infer_representation_name
|
100
|
+
name.split('::').last.
|
94
101
|
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
95
102
|
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
96
103
|
downcase
|
@@ -44,9 +44,9 @@ module Representable
|
|
44
44
|
class ObjectBinding < Binding
|
45
45
|
def write(hash, value)
|
46
46
|
if definition.array?
|
47
|
-
hash
|
47
|
+
hash[definition.from] = value.collect {|v| v.to_hash(:wrap => false)}
|
48
48
|
else
|
49
|
-
hash.
|
49
|
+
hash[definition.from] = value.to_hash(:wrap => false)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
|
@@ -8,8 +8,6 @@ module Representable
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def read(xml)
|
11
|
-
xml = Nokogiri::XML::Node.from(xml) or return default
|
12
|
-
|
13
11
|
value_from_node(xml) or default
|
14
12
|
end
|
15
13
|
|
@@ -22,16 +20,6 @@ module Representable
|
|
22
20
|
definition.from
|
23
21
|
end
|
24
22
|
|
25
|
-
def wrap(xml, opts = {:always_create => false})
|
26
|
-
wrap_with = @auto_vals ? auto_wrapper : definition.wrapper
|
27
|
-
|
28
|
-
return xml if !wrap_with || xml.name == wrap_with
|
29
|
-
if !opts[:always_create] && (child = xml.children.find {|c| c.name == wrap_with })
|
30
|
-
return child
|
31
|
-
end
|
32
|
-
xml.add_node(wrap_with.to_s)
|
33
|
-
end
|
34
|
-
|
35
23
|
def collect_for(xml)
|
36
24
|
nodes = xml.search("./#{xpath}")
|
37
25
|
vals = nodes.collect { |node| yield node }
|
@@ -44,9 +32,7 @@ module Representable
|
|
44
32
|
# Represents a tag attribute.
|
45
33
|
class AttributeBinding < Binding
|
46
34
|
def write(xml, values)
|
47
|
-
|
48
|
-
xml[definition.from] = values.to_s
|
49
|
-
end
|
35
|
+
xml[definition.from] = values.to_s
|
50
36
|
end
|
51
37
|
|
52
38
|
private
|
@@ -58,21 +44,13 @@ module Representable
|
|
58
44
|
|
59
45
|
# Represents text content in a tag. # FIXME: is this tested???
|
60
46
|
class TextBinding < Binding
|
61
|
-
# Updates the text in the given _xml_ block to
|
62
|
-
# the _value_ provided.
|
63
47
|
def write(xml, value)
|
64
|
-
|
65
|
-
|
66
|
-
add(xml,
|
67
|
-
elsif definition.name?
|
68
|
-
xml.name = value
|
69
|
-
elsif definition.array?
|
70
|
-
value.each do |v|
|
71
|
-
add(xml.add_node(definition.from), v)
|
72
|
-
end
|
73
|
-
else
|
74
|
-
add(xml.add_node(definition.from), value)
|
48
|
+
if definition.array?
|
49
|
+
value.each do |v|
|
50
|
+
add(xml, definition.from, v)
|
75
51
|
end
|
52
|
+
else
|
53
|
+
add(xml, definition.from, value)
|
76
54
|
end
|
77
55
|
end
|
78
56
|
|
@@ -82,9 +60,10 @@ module Representable
|
|
82
60
|
node.content
|
83
61
|
end
|
84
62
|
end
|
85
|
-
|
86
|
-
def add(
|
87
|
-
|
63
|
+
|
64
|
+
def add(xml, name, value)
|
65
|
+
child = xml.add_child Nokogiri::XML::Node.new(name, xml.document)
|
66
|
+
child.content = value
|
88
67
|
end
|
89
68
|
end
|
90
69
|
|
@@ -93,12 +72,10 @@ module Representable
|
|
93
72
|
class ObjectBinding < Binding
|
94
73
|
# Adds the ref's markup to +xml+.
|
95
74
|
def write(xml, value)
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
write_entity(xml, value)
|
101
|
-
end
|
75
|
+
if definition.array?
|
76
|
+
write_collection(xml, value)
|
77
|
+
else
|
78
|
+
write_entity(xml, value)
|
102
79
|
end
|
103
80
|
end
|
104
81
|
|
@@ -107,18 +84,10 @@ module Representable
|
|
107
84
|
[]
|
108
85
|
end
|
109
86
|
|
110
|
-
def serialize(object)
|
111
|
-
object.to_xml
|
112
|
-
end
|
113
|
-
|
114
|
-
def deserialize(node_class, xml)
|
115
|
-
node_class.from_xml(xml)
|
116
|
-
end
|
117
|
-
|
118
87
|
# Deserializes the ref's element from +xml+.
|
119
88
|
def value_from_node(xml)
|
120
89
|
collect_for(xml) do |node|
|
121
|
-
|
90
|
+
definition.sought_type.from_node(node)
|
122
91
|
end
|
123
92
|
end
|
124
93
|
|
@@ -129,7 +98,7 @@ module Representable
|
|
129
98
|
end
|
130
99
|
|
131
100
|
def write_entity(xml, entity)
|
132
|
-
xml.add_child(
|
101
|
+
xml.add_child(entity.to_node)
|
133
102
|
end
|
134
103
|
end
|
135
104
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Representable
|
2
|
-
class
|
3
|
-
|
2
|
+
# Created at class compile time. Keeps configuration options for one property.
|
3
|
+
class Definition
|
4
|
+
attr_reader :name, :sought_type, :from
|
4
5
|
alias_method :getter, :name
|
5
6
|
|
6
7
|
def initialize(sym, options={})
|
@@ -28,23 +29,10 @@ module Representable
|
|
28
29
|
sought_type.is_a?(Class)
|
29
30
|
end
|
30
31
|
|
31
|
-
def name?
|
32
|
-
@name == '*'
|
33
|
-
end
|
34
|
-
|
35
|
-
def content?
|
36
|
-
@name == '.'
|
37
|
-
end
|
38
|
-
|
39
32
|
def array?
|
40
33
|
@array
|
41
34
|
end
|
42
35
|
|
43
|
-
def cdata? # FIXME: move to XML!
|
44
|
-
@cdata
|
45
|
-
end
|
46
|
-
|
47
|
-
|
48
36
|
# Applies the block to +value+ which might also be a collection.
|
49
37
|
def apply(value)
|
50
38
|
return value unless value # DISCUSS: is that ok here?
|
data/lib/representable/json.rb
CHANGED
@@ -24,33 +24,26 @@ module Representable
|
|
24
24
|
(BINDING_FOR_TYPE[definition.sought_type] or ObjectBinding).new(definition)
|
25
25
|
end
|
26
26
|
|
27
|
-
# Creates a new
|
28
|
-
|
29
|
-
|
30
|
-
# book = Book.from_xml("<book><name>Beyond Java</name></book>")
|
31
|
-
# DISCUSS: assumes shitty wrapping like :article => {:name => ...}
|
32
|
-
def from_json(data, *args, &block)
|
33
|
-
create_from_json.tap do |object|
|
34
|
-
object.from_json(data, *args, &block)
|
35
|
-
end
|
27
|
+
# Creates a new object from the passed JSON document.
|
28
|
+
def from_json(*args, &block)
|
29
|
+
new.from_json(*args, &block)
|
36
30
|
end
|
37
31
|
|
38
|
-
def from_hash(
|
39
|
-
|
40
|
-
object.update_properties_from(data)
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
def create_from_json(*args)
|
46
|
-
new(*args)
|
32
|
+
def from_hash(*args, &block)
|
33
|
+
new.from_hash(*args, &block)
|
47
34
|
end
|
48
35
|
end
|
49
36
|
|
50
|
-
|
37
|
+
# Parses the body as JSON and delegates to #from_hash.
|
38
|
+
def from_json(data, *args, &block)
|
51
39
|
data = ::JSON[data]
|
52
|
-
data
|
53
|
-
|
40
|
+
from_hash(data, *args, &block)
|
41
|
+
end
|
42
|
+
|
43
|
+
def from_hash(data, options={}, &block)
|
44
|
+
if wrap = options[:wrap] || self.class.representation_wrap
|
45
|
+
data = data[wrap.to_s]
|
46
|
+
end
|
54
47
|
|
55
48
|
update_properties_from(data, &block)
|
56
49
|
end
|
@@ -58,8 +51,9 @@ module Representable
|
|
58
51
|
def to_hash(options={})
|
59
52
|
hash = create_representation_with({})
|
60
53
|
|
61
|
-
|
62
|
-
|
54
|
+
return hash unless wrap = options[:wrap] || self.class.representation_wrap
|
55
|
+
|
56
|
+
{wrap => hash}
|
63
57
|
end
|
64
58
|
|
65
59
|
# Returns a JSON string representing this object.
|
data/lib/representable/xml.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'representable'
|
2
|
-
require 'representable/nokogiri_extensions'
|
3
2
|
require 'representable/bindings/xml_bindings'
|
4
3
|
|
5
4
|
module Representable
|
@@ -13,20 +12,12 @@ module Representable
|
|
13
12
|
base.class_eval do
|
14
13
|
include Representable
|
15
14
|
extend ClassMethods
|
15
|
+
self.representation_wrap = true # let representable compute it.
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
19
|
|
20
|
-
class Definition < Representable::Definition
|
21
|
-
# FIXME: extract xml-specific from Definition.
|
22
|
-
end
|
23
|
-
|
24
|
-
|
25
20
|
module ClassMethods
|
26
|
-
def definition_class
|
27
|
-
Definition
|
28
|
-
end
|
29
|
-
|
30
21
|
def binding_for_definition(definition)
|
31
22
|
(BINDING_FOR_TYPE[definition.sought_type] or ObjectBinding).new(definition)
|
32
23
|
end
|
@@ -38,29 +29,34 @@ module Representable
|
|
38
29
|
#
|
39
30
|
# Example:
|
40
31
|
# band.from_xml("<band><name>Nofx</name></band>")
|
41
|
-
def from_xml(
|
42
|
-
|
43
|
-
object.from_xml(doc, *args, &block)
|
44
|
-
end
|
32
|
+
def from_xml(*args, &block)
|
33
|
+
new.from_xml(*args, &block)
|
45
34
|
end
|
46
35
|
|
47
|
-
|
48
|
-
|
49
|
-
new(*args)
|
36
|
+
def from_node(*args, &block)
|
37
|
+
new.from_node(*args, &block)
|
50
38
|
end
|
51
39
|
end
|
52
40
|
|
41
|
+
|
53
42
|
def from_xml(doc, *args, &block)
|
54
|
-
|
55
|
-
|
43
|
+
node = Nokogiri::XML(doc).root
|
44
|
+
from_node(node, *args, &block)
|
56
45
|
end
|
57
46
|
|
47
|
+
def from_node(node, options={}, &block)
|
48
|
+
update_properties_from(node, &block)
|
49
|
+
end
|
58
50
|
|
59
51
|
# Returns a Nokogiri::XML object representing this object.
|
60
|
-
def
|
61
|
-
root_tag =
|
52
|
+
def to_node(options={}, &block)
|
53
|
+
root_tag = options[:wrap] || self.class.representation_wrap
|
62
54
|
|
63
|
-
create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, Nokogiri::XML::Document.new))
|
55
|
+
create_representation_with(Nokogiri::XML::Node.new(root_tag.to_s, Nokogiri::XML::Document.new), &block)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_xml(*args, &block)
|
59
|
+
to_node(*args, &block).to_s
|
64
60
|
end
|
65
61
|
end
|
66
62
|
end
|
data/test/bindings_test.rb
CHANGED
@@ -13,7 +13,7 @@ class BindingsTest < MiniTest::Spec
|
|
13
13
|
|
14
14
|
|
15
15
|
describe "TextRef#read" do
|
16
|
-
def parse_xml(xml); Nokogiri::XML
|
16
|
+
def parse_xml(xml); Nokogiri::XML(xml).root; end
|
17
17
|
|
18
18
|
before do
|
19
19
|
@ref = Representable::XML::TextBinding.new(Representable::Definition.new(:song))
|
data/test/json_test.rb
CHANGED
@@ -9,7 +9,6 @@ module JsonTest
|
|
9
9
|
describe "JSON module" do
|
10
10
|
before do
|
11
11
|
@Band = Class.new(Band) do
|
12
|
-
self.representation_name= :band
|
13
12
|
representable_property :label
|
14
13
|
end
|
15
14
|
end
|
@@ -42,35 +41,50 @@ module JsonTest
|
|
42
41
|
end
|
43
42
|
|
44
43
|
it "accepts json string" do
|
45
|
-
@band.from_json({
|
44
|
+
@band.from_json({name: "Nofx", label: "NOFX"}.to_json)
|
46
45
|
assert_equal ["Nofx", "NOFX"], [@band.name, @band.label]
|
47
46
|
end
|
48
47
|
|
49
48
|
it "forwards block to #update_properties_from" do
|
50
|
-
@band.from_json({
|
49
|
+
@band.from_json({name: "Nofx", label: "NOFX"}.to_json) do |binding|
|
51
50
|
binding.definition.name == "name"
|
52
51
|
end
|
53
52
|
|
54
53
|
assert_equal ["Nofx", nil], [@band.name, @band.label]
|
55
54
|
end
|
56
55
|
|
57
|
-
|
58
|
-
|
59
|
-
band.from_json({:
|
60
|
-
assert_equal "This Is A Standoff", band.name
|
56
|
+
|
57
|
+
it "doesn't use wrap per default" do
|
58
|
+
@band.from_json({:name => "This Is A Standoff"}.to_json)
|
59
|
+
assert_equal "This Is A Standoff", @band.name
|
60
|
+
end
|
61
|
+
|
62
|
+
it "respects :wrap option" do
|
63
|
+
@band.from_json({:band => {:name => "This Is A Standoff"}}.to_json, :wrap => :band)
|
64
|
+
assert_equal "This Is A Standoff", @band.name
|
65
|
+
end
|
66
|
+
|
67
|
+
it "respects :wrap option over representation_wrap" do
|
68
|
+
@Band.class_eval do
|
69
|
+
self.representation_wrap = :group
|
70
|
+
end
|
71
|
+
@band.from_json({:band => {:name => "This Is A Standoff"}}.to_json, :wrap => :band)
|
72
|
+
assert_equal "This Is A Standoff", @band.name
|
61
73
|
end
|
62
74
|
|
63
|
-
it "
|
64
|
-
|
65
|
-
|
66
|
-
|
75
|
+
it "respects representation_wrap" do
|
76
|
+
@Band.class_eval do
|
77
|
+
self.representation_wrap = :group
|
78
|
+
end
|
79
|
+
@band.from_json({:group => {:name => "This Is A Standoff"}}.to_json)
|
80
|
+
assert_equal "This Is A Standoff", @band.name
|
67
81
|
end
|
68
82
|
end
|
69
83
|
|
70
84
|
|
71
85
|
describe ".from_json" do
|
72
86
|
it "delegates to #from_json after object conception" do
|
73
|
-
band = @Band.from_json({
|
87
|
+
band = @Band.from_json({name: "Nofx", label: "NOFX"}.to_json) do |binding| binding.definition.name == "name" end
|
74
88
|
assert_equal ["Nofx", nil], [band.name, band.label]
|
75
89
|
end
|
76
90
|
|
@@ -95,12 +109,43 @@ module JsonTest
|
|
95
109
|
end
|
96
110
|
end
|
97
111
|
|
112
|
+
|
98
113
|
describe ".from_hash" do
|
99
114
|
it "accepts unwrapped hash with string keys" do
|
100
115
|
band = Band.from_hash("name" => "Bombshell Rocks")
|
101
116
|
assert_equal "Bombshell Rocks", band.name
|
102
117
|
end
|
103
118
|
end
|
119
|
+
|
120
|
+
|
121
|
+
describe "#to_json" do
|
122
|
+
before do
|
123
|
+
@band = @Band.new
|
124
|
+
@band.label = "Fat"
|
125
|
+
end
|
126
|
+
|
127
|
+
it "doesn't wrap per default" do
|
128
|
+
assert_equal "{\"label\":\"Fat\"}", @band.to_json
|
129
|
+
end
|
130
|
+
|
131
|
+
it "respects :wrap option" do
|
132
|
+
assert_equal "{\"band\":{\"label\":\"Fat\"}}", @band.to_json(:wrap => :band)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "respects :wrap option over representation_wrap" do
|
136
|
+
@Band.class_eval do
|
137
|
+
self.representation_wrap = :group
|
138
|
+
end
|
139
|
+
assert_equal "{\"band\":{\"label\":\"Fat\"}}", @band.to_json(:wrap => :band)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "respects representation_wrap" do
|
143
|
+
@Band.class_eval do
|
144
|
+
self.representation_wrap = :group
|
145
|
+
end
|
146
|
+
assert_equal "{\"group\":{\"label\":\"Fat\"}}", @band.to_json
|
147
|
+
end
|
148
|
+
end
|
104
149
|
end
|
105
150
|
end
|
106
151
|
|
@@ -112,7 +157,7 @@ module JsonTest
|
|
112
157
|
end
|
113
158
|
|
114
159
|
it "#from_json creates correct accessors" do
|
115
|
-
band = Band.from_json({:
|
160
|
+
band = Band.from_json({:name => "Bombshell Rocks"}.to_json)
|
116
161
|
assert_equal "Bombshell Rocks", band.name
|
117
162
|
end
|
118
163
|
|
@@ -120,7 +165,7 @@ module JsonTest
|
|
120
165
|
band = Band.new
|
121
166
|
band.name = "Cigar"
|
122
167
|
|
123
|
-
assert_equal '{"
|
168
|
+
assert_equal '{"name":"Cigar"}', band.to_json
|
124
169
|
end
|
125
170
|
end
|
126
171
|
|
@@ -136,7 +181,7 @@ module JsonTest
|
|
136
181
|
end
|
137
182
|
|
138
183
|
it "#from_json creates one Item instance" do
|
139
|
-
album = Album.from_json('{"
|
184
|
+
album = Album.from_json('{"label":{"name":"Fat Wreck"}}')
|
140
185
|
assert_equal "Fat Wreck", album.label.name
|
141
186
|
end
|
142
187
|
|
@@ -144,7 +189,23 @@ module JsonTest
|
|
144
189
|
label = Label.new; label.name = "Fat Wreck"
|
145
190
|
album = Album.new; album.label = label
|
146
191
|
|
147
|
-
assert_equal '{"
|
192
|
+
assert_equal '{"label":{"name":"Fat Wreck"}}', album.to_json
|
193
|
+
end
|
194
|
+
|
195
|
+
describe ":different_name, :as => Label" do
|
196
|
+
before do
|
197
|
+
@Album = Class.new do
|
198
|
+
include Representable::JSON
|
199
|
+
representable_property :seller, :as => Label
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
it "#to_xml respects the different name" do
|
204
|
+
label = Label.new; label.name = "Fat Wreck"
|
205
|
+
album = @Album.new; album.seller = label
|
206
|
+
|
207
|
+
assert_equal "{\"seller\":{\"name\":\"Fat Wreck\"}}", album.to_json(:wrap => false)
|
208
|
+
end
|
148
209
|
end
|
149
210
|
end
|
150
211
|
|
@@ -155,13 +216,13 @@ module JsonTest
|
|
155
216
|
end
|
156
217
|
|
157
218
|
it "respects :from in #from_json" do
|
158
|
-
song = Song.from_json({:
|
219
|
+
song = Song.from_json({:songName => "Run To The Hills"}.to_json)
|
159
220
|
assert_equal "Run To The Hills", song.name
|
160
221
|
end
|
161
222
|
|
162
223
|
it "respects :from in #to_json" do
|
163
224
|
song = Song.new; song.name = "Run To The Hills"
|
164
|
-
assert_equal '{"
|
225
|
+
assert_equal '{"songName":"Run To The Hills"}', song.to_json
|
165
226
|
end
|
166
227
|
end
|
167
228
|
end
|
@@ -175,7 +236,7 @@ module JsonTest
|
|
175
236
|
end
|
176
237
|
|
177
238
|
it "#from_json creates correct accessors" do
|
178
|
-
cd = CD.from_json({:
|
239
|
+
cd = CD.from_json({:songs => ["Out in the cold", "Microphone"]}.to_json)
|
179
240
|
assert_equal ["Out in the cold", "Microphone"], cd.songs
|
180
241
|
end
|
181
242
|
|
@@ -183,7 +244,7 @@ module JsonTest
|
|
183
244
|
cd = CD.new
|
184
245
|
cd.songs = ["Out in the cold", "Microphone"]
|
185
246
|
|
186
|
-
assert_equal '{"
|
247
|
+
assert_equal '{"songs":["Out in the cold","Microphone"]}', cd.to_json
|
187
248
|
end
|
188
249
|
end
|
189
250
|
|
@@ -204,9 +265,9 @@ module JsonTest
|
|
204
265
|
|
205
266
|
describe "#from_json" do
|
206
267
|
it "pushes collection items to array" do
|
207
|
-
cd = Compilation.from_json({:
|
268
|
+
cd = Compilation.from_json({:bands => [
|
208
269
|
{:name => "Cobra Skulls"},
|
209
|
-
{:name => "Diesel Boy"}]}
|
270
|
+
{:name => "Diesel Boy"}]}.to_json)
|
210
271
|
assert_equal ["Cobra Skulls", "Diesel Boy"], cd.bands.map(&:name).sort
|
211
272
|
end
|
212
273
|
|
@@ -220,7 +281,7 @@ module JsonTest
|
|
220
281
|
cd = Compilation.new
|
221
282
|
cd.bands = [Band.new("Diesel Boy"), Band.new("Bad Religion")]
|
222
283
|
|
223
|
-
assert_equal '{"
|
284
|
+
assert_equal '{"bands":[{"name":"Diesel Boy"},{"name":"Bad Religion"}]}', cd.to_json
|
224
285
|
end
|
225
286
|
end
|
226
287
|
|
@@ -232,7 +293,7 @@ module JsonTest
|
|
232
293
|
end
|
233
294
|
|
234
295
|
it "respects :from in #from_json" do
|
235
|
-
songs = Songs.from_json({:
|
296
|
+
songs = Songs.from_json({:songList => ["Out in the cold", "Microphone"]}.to_json)
|
236
297
|
assert_equal ["Out in the cold", "Microphone"], songs.tracks
|
237
298
|
end
|
238
299
|
|
@@ -240,7 +301,7 @@ module JsonTest
|
|
240
301
|
songs = Songs.new
|
241
302
|
songs.tracks = ["Out in the cold", "Microphone"]
|
242
303
|
|
243
|
-
assert_equal '{"
|
304
|
+
assert_equal '{"songList":["Out in the cold","Microphone"]}', songs.to_json
|
244
305
|
end
|
245
306
|
end
|
246
307
|
end
|
data/test/representable_test.rb
CHANGED
@@ -58,7 +58,7 @@ class RepresentableTest < MiniTest::Spec
|
|
58
58
|
end
|
59
59
|
|
60
60
|
|
61
|
-
describe "#
|
61
|
+
describe "#representation_wrap" do
|
62
62
|
class SoundSystem
|
63
63
|
include Representable
|
64
64
|
end
|
@@ -70,14 +70,24 @@ class RepresentableTest < MiniTest::Spec
|
|
70
70
|
class SoftcoreBand < HardcoreBand
|
71
71
|
end
|
72
72
|
|
73
|
-
it "
|
74
|
-
assert_equal
|
73
|
+
it "returns false per default" do
|
74
|
+
assert_equal nil, SoundSystem.representation_wrap
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
it "infers a printable class name if set to true" do
|
79
|
+
HardcoreBand.representation_wrap = true
|
80
|
+
assert_equal "hardcore_band", HardcoreBand.send(:representation_wrap)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "can be set explicitely" do
|
84
|
+
HardcoreBand.representation_wrap = "breach"
|
85
|
+
assert_equal "breach", HardcoreBand.representation_wrap
|
75
86
|
end
|
76
87
|
|
77
88
|
it "inherits correctly" do
|
78
|
-
HardcoreBand.
|
79
|
-
assert_equal "breach",
|
80
|
-
assert_equal "breach", SoftcoreBand.representation_name
|
89
|
+
HardcoreBand.representation_wrap = "breach"
|
90
|
+
assert_equal "breach", SoftcoreBand.representation_wrap
|
81
91
|
end
|
82
92
|
end
|
83
93
|
|
data/test/xml_test.rb
CHANGED
@@ -10,9 +10,12 @@ class Band
|
|
10
10
|
end
|
11
11
|
end
|
12
12
|
|
13
|
-
class
|
14
|
-
|
15
|
-
|
13
|
+
class Album
|
14
|
+
include Representable::XML
|
15
|
+
representable_property :band, :as => Band
|
16
|
+
|
17
|
+
def initialize(band=nil)
|
18
|
+
band and self.band = band
|
16
19
|
end
|
17
20
|
end
|
18
21
|
|
@@ -25,48 +28,65 @@ class XmlTest < MiniTest::Spec
|
|
25
28
|
before do
|
26
29
|
@Band = Class.new do
|
27
30
|
include Representable::XML
|
28
|
-
self.
|
31
|
+
self.representation_wrap = :band
|
29
32
|
representable_property :name
|
30
33
|
representable_property :label
|
31
34
|
end
|
32
35
|
end
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
|
37
|
+
|
38
|
+
describe ".from_xml" do
|
39
|
+
it "is delegated to #from_xml" do
|
40
|
+
block = lambda {|bind|}
|
41
|
+
@Band.any_instance.expects(:from_xml).with("{}", "yo") # FIXME: how to expect block?
|
42
|
+
@Band.from_xml("{}", "yo", &block)
|
43
|
+
end
|
38
44
|
end
|
39
45
|
|
40
|
-
|
41
|
-
|
46
|
+
|
47
|
+
describe ".from_node" do
|
48
|
+
it "is delegated to #from_node" do
|
49
|
+
block = lambda {|bind|}
|
50
|
+
@Band.any_instance.expects(:from_node).with("{}", "yo") # FIXME: how to expect block?
|
51
|
+
@Band.from_node("{}", "yo", &block)
|
52
|
+
end
|
42
53
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
54
|
+
|
55
|
+
|
56
|
+
describe "#from_xml" do
|
57
|
+
before do
|
58
|
+
@band = @Band.new
|
59
|
+
@xml = %{<band><name>Nofx</name><label>NOFX</label></band>}
|
47
60
|
end
|
48
61
|
|
49
|
-
it "
|
50
|
-
|
62
|
+
it "parses XML and assigns properties" do
|
63
|
+
@band.from_xml(@xml)
|
64
|
+
assert_equal ["Nofx", "NOFX"], [@band.name, @band.label]
|
51
65
|
end
|
52
66
|
|
53
|
-
it "
|
54
|
-
|
67
|
+
it "forwards block to #from_node" do
|
68
|
+
@band.from_xml(@xml) do |binding|
|
69
|
+
binding.definition.name == "name"
|
70
|
+
end
|
71
|
+
|
72
|
+
assert_equal ["Nofx", nil], [@band.name, @band.label]
|
55
73
|
end
|
56
74
|
end
|
57
75
|
|
58
|
-
|
76
|
+
|
77
|
+
describe "#from_node" do
|
59
78
|
before do
|
60
79
|
@band = @Band.new
|
80
|
+
@xml = Nokogiri::XML(%{<band><name>Nofx</name><label>NOFX</label></band>}).root
|
61
81
|
end
|
62
82
|
|
63
|
-
it "
|
64
|
-
@band.
|
83
|
+
it "receives Nokogiri node and assigns properties" do
|
84
|
+
@band.from_node(@xml)
|
65
85
|
assert_equal ["Nofx", "NOFX"], [@band.name, @band.label]
|
66
86
|
end
|
67
87
|
|
68
88
|
it "forwards block to #update_properties_from" do
|
69
|
-
@band.
|
89
|
+
@band.from_node(@xml) do |binding|
|
70
90
|
binding.definition.name == "name"
|
71
91
|
end
|
72
92
|
|
@@ -74,17 +94,61 @@ class XmlTest < MiniTest::Spec
|
|
74
94
|
end
|
75
95
|
end
|
76
96
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
97
|
+
|
98
|
+
describe "#to_xml" do
|
99
|
+
it "delegates to #to_node and returns string" do
|
100
|
+
assert_xml_equal "<band><name>Rise Against</name></band>", Band.new("Rise Against").to_xml
|
101
|
+
end
|
102
|
+
|
103
|
+
it "forwards block to #to_node" do
|
104
|
+
band = @Band.new
|
105
|
+
band.name = "The Guinea Pigs"
|
106
|
+
band.label = "n/a"
|
107
|
+
xml = band.to_xml do |binding|
|
108
|
+
binding.definition.name == "name"
|
109
|
+
end
|
110
|
+
|
111
|
+
assert_xml_equal "<band><name>The Guinea Pigs</name></band>", xml
|
82
112
|
end
|
83
113
|
end
|
84
114
|
|
115
|
+
|
116
|
+
describe "#to_node" do
|
117
|
+
it "returns Nokogiri node" do
|
118
|
+
node = Band.new("Rise Against").to_node
|
119
|
+
assert_kind_of Nokogiri::XML::Element, node
|
120
|
+
end
|
121
|
+
|
122
|
+
it "wraps with infered class name per default" do
|
123
|
+
node = Band.new("Rise Against").to_node
|
124
|
+
assert_xml_equal "<band><name>Rise Against</name></band>", node.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
it "respects #representation_wrap=" do
|
128
|
+
klass = Class.new(Band)
|
129
|
+
klass.representation_wrap = :group
|
130
|
+
assert_xml_equal "<group><name>Rise Against</name></group>", klass.new("Rise Against").to_node.to_s
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
describe "#binding_for_definition" do
|
136
|
+
it "returns AttributeBinding" do
|
137
|
+
assert_kind_of XML::AttributeBinding, @Band.binding_for_definition(Def.new(:band, :from => "@band"))
|
138
|
+
end
|
139
|
+
|
140
|
+
it "returns ObjectBinding" do
|
141
|
+
assert_kind_of XML::ObjectBinding, @Band.binding_for_definition(Def.new(:band, :as => Hash))
|
142
|
+
end
|
143
|
+
|
144
|
+
it "returns TextBinding" do
|
145
|
+
assert_kind_of XML::TextBinding, @Band.binding_for_definition(Def.new(:band, :from => :content))
|
146
|
+
end
|
147
|
+
end
|
85
148
|
end
|
86
149
|
end
|
87
150
|
|
151
|
+
|
88
152
|
class AttributesTest < MiniTest::Spec
|
89
153
|
describe ":from => @rel" do
|
90
154
|
class Link
|
@@ -105,19 +169,13 @@ class AttributesTest < MiniTest::Spec
|
|
105
169
|
link = Link.new
|
106
170
|
link.href = "http://apotomo.de/"
|
107
171
|
|
108
|
-
assert_xml_equal %{<link href="http://apotomo.de/">}, link.to_xml
|
172
|
+
assert_xml_equal %{<link href="http://apotomo.de/">}, link.to_xml
|
109
173
|
end
|
110
174
|
end
|
111
175
|
end
|
112
176
|
|
113
177
|
class TypedPropertyTest < MiniTest::Spec
|
114
178
|
describe ":as => Item" do
|
115
|
-
class Album
|
116
|
-
include Representable::XML
|
117
|
-
representable_property :band, :as => Band
|
118
|
-
representable_property :label, :as => Label
|
119
|
-
end
|
120
|
-
|
121
179
|
it "#from_xml creates one Item instance" do
|
122
180
|
album = Album.from_xml(%{
|
123
181
|
<album>
|
@@ -129,25 +187,25 @@ class TypedPropertyTest < MiniTest::Spec
|
|
129
187
|
|
130
188
|
describe "#to_xml" do
|
131
189
|
it "doesn't escape xml from Band#to_xml" do
|
132
|
-
band = Band.new
|
133
|
-
|
134
|
-
album = Album.new
|
135
|
-
album.band = band
|
190
|
+
band = Band.new("Bad Religion")
|
191
|
+
album = Album.new(band)
|
136
192
|
|
137
193
|
assert_xml_equal %{<album>
|
138
194
|
<band>
|
139
195
|
<name>Bad Religion</name>
|
140
196
|
</band>
|
141
|
-
</album>}, album.to_xml
|
197
|
+
</album>}, album.to_xml
|
142
198
|
end
|
143
199
|
|
144
|
-
it "doesn't escape and wrap string from
|
145
|
-
|
146
|
-
|
200
|
+
it "doesn't escape and wrap string from Band#to_node" do
|
201
|
+
band = Band.new("Bad Religion")
|
202
|
+
band.instance_eval do
|
203
|
+
def to_node
|
204
|
+
"<band>Baaaad Religion</band>"
|
205
|
+
end
|
206
|
+
end
|
147
207
|
|
148
|
-
assert_xml_equal %{<album>
|
149
|
-
<label>Fat Wreck</label>
|
150
|
-
</album>}, album.to_xml.to_s
|
208
|
+
assert_xml_equal %{<album><band>Baaaad Religion</band></album>}, Album.new(band).to_xml
|
151
209
|
end
|
152
210
|
end
|
153
211
|
end
|
@@ -155,19 +213,12 @@ end
|
|
155
213
|
|
156
214
|
|
157
215
|
class CollectionTest < MiniTest::Spec
|
158
|
-
describe ":as =>
|
216
|
+
describe ":as => Band, :from => :band, :collection => true" do
|
159
217
|
class Compilation
|
160
218
|
include Representable::XML
|
161
219
|
representable_collection :bands, :as => Band, :from => :band
|
162
220
|
end
|
163
221
|
|
164
|
-
describe "#representable_collection" do
|
165
|
-
it "declares a collection" do
|
166
|
-
assert Compilation.representable_attrs.first.array?
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
|
171
222
|
describe "#from_xml" do
|
172
223
|
it "pushes collection items to array" do
|
173
224
|
cd = Compilation.from_xml(%{
|
@@ -195,12 +246,12 @@ class CollectionTest < MiniTest::Spec
|
|
195
246
|
assert_xml_equal %{<compilation>
|
196
247
|
<band><name>Diesel Boy</name></band>
|
197
248
|
<band><name>Bad Religion</name></band>
|
198
|
-
</compilation>}, cd.to_xml
|
249
|
+
</compilation>}, cd.to_xml
|
199
250
|
end
|
200
251
|
end
|
201
252
|
|
202
253
|
|
203
|
-
describe ":
|
254
|
+
describe ":from" do
|
204
255
|
class Album
|
205
256
|
include Representable::XML
|
206
257
|
representable_collection :songs, :from => :song
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 10
|
8
|
+
- 0
|
9
|
+
version: 0.10.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Nick Sutterer
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-11-
|
17
|
+
date: 2011-11-29 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -133,7 +133,6 @@ files:
|
|
133
133
|
- lib/representable/bindings/xml_bindings.rb
|
134
134
|
- lib/representable/definition.rb
|
135
135
|
- lib/representable/json.rb
|
136
|
-
- lib/representable/nokogiri_extensions.rb
|
137
136
|
- lib/representable/version.rb
|
138
137
|
- lib/representable/xml.rb
|
139
138
|
- representable.gemspec
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'nokogiri'
|
2
|
-
|
3
|
-
Nokogiri::XML::Node.class_eval do
|
4
|
-
def add_node(name)
|
5
|
-
add_child Nokogiri::XML::Node.new(name, document)
|
6
|
-
end
|
7
|
-
|
8
|
-
# FIXME: remove switch. where is #from used with nodes?
|
9
|
-
def self.from(data)
|
10
|
-
case data
|
11
|
-
when Nokogiri::XML::Node
|
12
|
-
data
|
13
|
-
when Nokogiri::XML::Document
|
14
|
-
data.root
|
15
|
-
else
|
16
|
-
Nokogiri::XML(data).root
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|