happymapper 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/Rakefile +2 -8
- data/examples/current_weather.rb +1 -1
- data/examples/multi_street_address.rb +17 -2
- data/lib/happymapper.rb +188 -5
- data/lib/happymapper/version.rb +1 -1
- data/spec/fixtures/multi_street_address.xml +1 -1
- data/spec/happymapper_attribute_spec.rb +3 -3
- data/spec/happymapper_element_spec.rb +3 -3
- data/spec/happymapper_item_spec.rb +20 -20
- data/spec/happymapper_spec.rb +9 -2
- data/spec/happymapper_to_xml_namespaces_spec.rb +149 -0
- data/spec/happymapper_to_xml_spec.rb +138 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/support/models.rb +12 -0
- metadata +20 -28
data/README.rdoc
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
== DESCRIPTION:
|
4
4
|
|
5
|
-
|
5
|
+
XML to object mapping library. I have included examples to help get you going. The specs should also point you in the right direction.
|
6
6
|
|
7
7
|
== FEATURES:
|
8
8
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
2
4
|
require 'rake'
|
3
|
-
require 'rake/rdoctask'
|
4
5
|
require 'spec/rake/spectask'
|
5
6
|
require File.expand_path('../lib/happymapper/version', __FILE__)
|
6
7
|
|
@@ -32,10 +33,3 @@ desc 'Upload website files to rubyforge'
|
|
32
33
|
task :website do
|
33
34
|
sh %{rsync -av website/ jnunemaker@rubyforge.org:/var/www/gforge-projects/happymapper}
|
34
35
|
end
|
35
|
-
|
36
|
-
Rake::RDocTask.new do |r|
|
37
|
-
r.title = 'HappyMapper Docs'
|
38
|
-
r.main = 'README.rdoc'
|
39
|
-
r.rdoc_dir = 'doc'
|
40
|
-
r.rdoc_files.include("README.rdoc", "lib/**/*.rb")
|
41
|
-
end
|
data/examples/current_weather.rb
CHANGED
@@ -7,7 +7,7 @@ class CurrentWeather
|
|
7
7
|
include HappyMapper
|
8
8
|
|
9
9
|
tag 'ob'
|
10
|
-
namespace 'aws'
|
10
|
+
namespace 'http://www.aws.com/aws'
|
11
11
|
element :temperature, Integer, :tag => 'temp'
|
12
12
|
element :feels_like, Integer, :tag => 'feels-like'
|
13
13
|
element :current_condition, String, :tag => 'current-condition', :attributes => {:icon => String}
|
@@ -6,10 +6,25 @@ file_contents = File.read(dir + '/../spec/fixtures/multi_street_address.xml')
|
|
6
6
|
class MultiStreetAddress
|
7
7
|
include HappyMapper
|
8
8
|
|
9
|
+
tag 'address'
|
10
|
+
|
9
11
|
# allow primitive type to be collection
|
10
12
|
has_many :street_address, String, :tag => "streetaddress"
|
11
13
|
element :city, String
|
12
|
-
element :
|
14
|
+
element :state_or_province, String, :tag => "stateOrProvince"
|
13
15
|
element :zip, String
|
14
16
|
element :country, String
|
15
|
-
end
|
17
|
+
end
|
18
|
+
|
19
|
+
multi = MultiStreetAddress.parse(file_contents)
|
20
|
+
|
21
|
+
puts "Street Address:"
|
22
|
+
|
23
|
+
multi.street_address.each do |street|
|
24
|
+
puts street
|
25
|
+
end
|
26
|
+
|
27
|
+
puts "City: #{multi.city}"
|
28
|
+
puts "State/Province: #{multi.state_or_province}"
|
29
|
+
puts "Zip: #{multi.zip}"
|
30
|
+
puts "Country: #{multi.country}"
|
data/lib/happymapper.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'rubygems'
|
1
2
|
require 'date'
|
2
3
|
require 'time'
|
3
4
|
require 'xml'
|
@@ -11,6 +12,8 @@ module HappyMapper
|
|
11
12
|
def self.included(base)
|
12
13
|
base.instance_variable_set("@attributes", {})
|
13
14
|
base.instance_variable_set("@elements", {})
|
15
|
+
base.instance_variable_set("@registered_namespaces", {})
|
16
|
+
|
14
17
|
base.extend ClassMethods
|
15
18
|
end
|
16
19
|
|
@@ -65,6 +68,10 @@ module HappyMapper
|
|
65
68
|
@namespace = namespace if namespace
|
66
69
|
@namespace
|
67
70
|
end
|
71
|
+
|
72
|
+
def register_namespace(namespace, ns)
|
73
|
+
@registered_namespaces.merge!(namespace => ns)
|
74
|
+
end
|
68
75
|
|
69
76
|
def tag(new_tag_name)
|
70
77
|
@tag_name = new_tag_name.to_s
|
@@ -100,12 +107,12 @@ module HappyMapper
|
|
100
107
|
|
101
108
|
attributes.each do |attr|
|
102
109
|
obj.send("#{attr.method_name}=",
|
103
|
-
|
110
|
+
attr.from_xml_node(n, namespace))
|
104
111
|
end
|
105
112
|
|
106
113
|
elements.each do |elem|
|
107
114
|
obj.send("#{elem.method_name}=",
|
108
|
-
|
115
|
+
elem.from_xml_node(n, namespace))
|
109
116
|
end
|
110
117
|
|
111
118
|
obj.send("#{@content}=", n.content) if @content
|
@@ -124,9 +131,185 @@ module HappyMapper
|
|
124
131
|
collection
|
125
132
|
end
|
126
133
|
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
#
|
138
|
+
# Create an xml representation of the specified class based on defined
|
139
|
+
# HappyMapper elements and attributes. The method is defined in a way
|
140
|
+
# that it can be called recursively by classes that are also HappyMapper
|
141
|
+
# classes, allowg for the composition of classes.
|
142
|
+
#
|
143
|
+
def to_xml(parent_node = nil, default_namespace = nil)
|
144
|
+
|
145
|
+
#
|
146
|
+
# Create a tag that uses the tag name of the class that has no contents
|
147
|
+
# but has the specified namespace or uses the default namespace
|
148
|
+
#
|
149
|
+
current_node = XML::Node.new(self.class.tag_name)
|
150
|
+
|
151
|
+
|
152
|
+
if parent_node
|
153
|
+
#
|
154
|
+
# if #to_xml has been called with a parent_node that means this method
|
155
|
+
# is being called recursively (or a special case) and we want to return
|
156
|
+
# the parent_node with the new node as a child
|
157
|
+
#
|
158
|
+
parent_node << current_node
|
159
|
+
else
|
160
|
+
#
|
161
|
+
# If #to_xml has been called without a Node (and namespace) that
|
162
|
+
# means we want to return an xml document
|
163
|
+
#
|
164
|
+
write_out_to_xml = true
|
165
|
+
end
|
166
|
+
|
167
|
+
#
|
168
|
+
# Add all the registered namespaces to the current node and the current node's
|
169
|
+
# root element. Without adding it to the root element it is not possible to
|
170
|
+
# parse or use xpath to find elements.
|
171
|
+
#
|
172
|
+
if self.class.instance_variable_get('@registered_namespaces')
|
173
|
+
|
174
|
+
# Given a node, continue moving up to parents until there are no more parents
|
175
|
+
find_root_node = lambda {|node| while node.parent? ; node = node.parent ; end ; node }
|
176
|
+
root_node = find_root_node.call(current_node)
|
177
|
+
|
178
|
+
# Add the registered namespace to the found root node only if it does not already have one defined
|
179
|
+
self.class.instance_variable_get('@registered_namespaces').each_pair do |prefix,href|
|
180
|
+
XML::Namespace.new(current_node,prefix,href)
|
181
|
+
XML::Namespace.new(root_node,prefix,href) unless root_node.namespaces.find_by_prefix(prefix)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
#
|
186
|
+
# Determine the tag namespace if one has been specified. This value takes
|
187
|
+
# precendence over one that is handed down to composed sub-classes.
|
188
|
+
#
|
189
|
+
tag_namespace = current_node.namespaces.find_by_prefix(self.class.namespace) || default_namespace
|
190
|
+
|
191
|
+
# Set the namespace of the current node to the specified namespace
|
192
|
+
current_node.namespaces.namespace = tag_namespace if tag_namespace
|
193
|
+
|
194
|
+
#
|
195
|
+
# Add all the attribute tags to the current node with their namespace, if one
|
196
|
+
# is defined, or the namespace handed down to the node.
|
197
|
+
#
|
198
|
+
self.class.attributes.each do |attribute|
|
199
|
+
attribute_namespace = current_node.namespaces.find_by_prefix(attribute.options[:namespace]) || default_namespace
|
200
|
+
|
201
|
+
value = send(attribute.method_name)
|
202
|
+
|
203
|
+
#
|
204
|
+
# If the attribute has a :on_save attribute defined that is a proc or
|
205
|
+
# a defined method, then call those with the current value.
|
206
|
+
#
|
207
|
+
if on_save_operation = attribute.options[:on_save]
|
208
|
+
if on_save_operation.is_a?(Proc)
|
209
|
+
value = on_save_operation.call(value)
|
210
|
+
elsif respond_to?(on_save_operation)
|
211
|
+
value = send(on_save_operation,value)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
current_node[ "#{attribute_namespace ? "#{attribute_namespace.prefix}:" : ""}#{attribute.tag}" ] = value
|
216
|
+
end
|
217
|
+
|
218
|
+
#
|
219
|
+
# All all the elements defined (e.g. has_one, has_many, element) ...
|
220
|
+
#
|
221
|
+
self.class.elements.each do |element|
|
222
|
+
|
223
|
+
tag = element.tag || element.name
|
224
|
+
|
225
|
+
element_namespace = current_node.namespaces.find_by_prefix(element.options[:namespace]) || tag_namespace
|
226
|
+
|
227
|
+
value = send(element.name)
|
228
|
+
|
229
|
+
#
|
230
|
+
# If the element defines an :on_save lambda/proc then we will call that
|
231
|
+
# operation on the specified value. This allows for operations to be
|
232
|
+
# performed to convert the value to a specific value to be saved to the xml.
|
233
|
+
#
|
234
|
+
if on_save_operation = element.options[:on_save]
|
235
|
+
if on_save_operation.is_a?(Proc)
|
236
|
+
value = on_save_operation.call(value)
|
237
|
+
elsif respond_to?(on_save_operation)
|
238
|
+
value = send(on_save_operation,value)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
#
|
243
|
+
# Normally a nil value would be ignored, however if specified then
|
244
|
+
# an empty element will be written to the xml
|
245
|
+
#
|
246
|
+
if value.nil? && element.options[:state_when_nil]
|
247
|
+
current_node << XML::Node.new(tag,nil,element_namespace)
|
248
|
+
end
|
249
|
+
|
250
|
+
#
|
251
|
+
# To allow for us to treat both groups of items and singular items
|
252
|
+
# equally we wrap the value and treat it as an array.
|
253
|
+
#
|
254
|
+
if value.nil?
|
255
|
+
values = []
|
256
|
+
elsif value.respond_to?(:to_ary) && !element.options[:single]
|
257
|
+
values = value.to_ary
|
258
|
+
else
|
259
|
+
values = [value]
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
values.each do |item|
|
264
|
+
|
265
|
+
if item.is_a?(HappyMapper)
|
266
|
+
|
267
|
+
#
|
268
|
+
# Other HappyMapper items that are convertable should not be called
|
269
|
+
# with the current node and the namespace defined for the element.
|
270
|
+
#
|
271
|
+
item.to_xml(current_node,element_namespace)
|
272
|
+
|
273
|
+
elsif item
|
274
|
+
|
275
|
+
#
|
276
|
+
# When a value exists we should append the value for the tag
|
277
|
+
#
|
278
|
+
current_node << XML::Node.new(tag,item.to_s,element_namespace)
|
279
|
+
|
280
|
+
else
|
281
|
+
|
282
|
+
#
|
283
|
+
# Normally a nil value would be ignored, however if specified then
|
284
|
+
# an empty element will be written to the xml
|
285
|
+
#
|
286
|
+
current_node << XML.Node.new(tag,nil,element_namespace) if element.options[:state_when_nil]
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
end
|
291
|
+
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
#
|
296
|
+
# Generate xml from a document if no node was passed as a parameter. Otherwise
|
297
|
+
# this method is being called recursively (or special case) and we should
|
298
|
+
# return the node with this node attached as a child.
|
299
|
+
#
|
300
|
+
if write_out_to_xml
|
301
|
+
document = XML::Document.new
|
302
|
+
document.root = current_node
|
303
|
+
document.to_s
|
304
|
+
else
|
305
|
+
parent_node
|
306
|
+
end
|
307
|
+
|
127
308
|
end
|
309
|
+
|
310
|
+
|
128
311
|
end
|
129
312
|
|
130
|
-
require 'happymapper/item'
|
131
|
-
require 'happymapper/attribute'
|
132
|
-
require 'happymapper/element'
|
313
|
+
require File.dirname(__FILE__) + '/happymapper/item'
|
314
|
+
require File.dirname(__FILE__) + '/happymapper/attribute'
|
315
|
+
require File.dirname(__FILE__) + '/happymapper/element'
|
data/lib/happymapper/version.rb
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe HappyMapper::Attribute do
|
4
4
|
describe "initialization" do
|
5
5
|
before do
|
6
6
|
@attr = HappyMapper::Attribute.new(:foo, String)
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
it 'should know that it is an attribute' do
|
10
10
|
@attr.attribute?.should be_true
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
it 'should know that it is NOT an element' do
|
14
14
|
@attr.element?.should be_false
|
15
15
|
end
|
@@ -1,15 +1,15 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe HappyMapper::Element do
|
4
4
|
describe "initialization" do
|
5
5
|
before do
|
6
6
|
@attr = HappyMapper::Element.new(:foo, String)
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
it 'should know that it is an element' do
|
10
10
|
@attr.element?.should be_true
|
11
11
|
end
|
12
|
-
|
12
|
+
|
13
13
|
it 'should know that it is NOT an attribute' do
|
14
14
|
@attr.attribute?.should be_false
|
15
15
|
end
|
@@ -1,75 +1,75 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
module Foo
|
4
4
|
class Bar; end
|
5
5
|
end
|
6
6
|
|
7
7
|
describe HappyMapper::Item do
|
8
|
-
|
8
|
+
|
9
9
|
describe "new instance" do
|
10
10
|
before do
|
11
11
|
@item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
it "should accept a name" do
|
15
15
|
@item.name.should == 'foo'
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
18
|
it 'should accept a type' do
|
19
19
|
@item.type.should == String
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
it 'should accept :tag as an option' do
|
23
23
|
@item.tag.should == 'foobar'
|
24
24
|
end
|
25
|
-
|
25
|
+
|
26
26
|
it "should have a method_name" do
|
27
27
|
@item.method_name.should == 'foo'
|
28
28
|
end
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
describe "#constant" do
|
32
32
|
it "should just use type if constant" do
|
33
33
|
item = HappyMapper::Item.new(:foo, String)
|
34
34
|
item.constant.should == String
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
it "should convert string type to constant" do
|
38
38
|
item = HappyMapper::Item.new(:foo, 'String')
|
39
39
|
item.constant.should == String
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it "should convert string with :: to constant" do
|
43
43
|
item = HappyMapper::Item.new(:foo, 'Foo::Bar')
|
44
44
|
item.constant.should == Foo::Bar
|
45
45
|
end
|
46
46
|
end
|
47
|
-
|
47
|
+
|
48
48
|
describe "#method_name" do
|
49
49
|
it "should convert dashes to underscores" do
|
50
50
|
item = HappyMapper::Item.new(:'foo-bar', String, :tag => 'foobar')
|
51
51
|
item.method_name.should == 'foo_bar'
|
52
52
|
end
|
53
53
|
end
|
54
|
-
|
54
|
+
|
55
55
|
describe "#xpath" do
|
56
56
|
it "should default to tag" do
|
57
57
|
item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
|
58
58
|
item.xpath.should == 'foobar'
|
59
59
|
end
|
60
|
-
|
60
|
+
|
61
61
|
it "should prepend with .// if options[:deep] true" do
|
62
62
|
item = HappyMapper::Item.new(:foo, String, :tag => 'foobar', :deep => true)
|
63
63
|
item.xpath.should == './/foobar'
|
64
64
|
end
|
65
|
-
|
65
|
+
|
66
66
|
it "should prepend namespace if namespace exists" do
|
67
67
|
item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
|
68
68
|
item.namespace = 'http://example.com'
|
69
69
|
item.xpath.should == 'happymapper:foobar'
|
70
70
|
end
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
describe "typecasting" do
|
74
74
|
it "should work with Strings" do
|
75
75
|
item = HappyMapper::Item.new(:foo, String)
|
@@ -77,36 +77,36 @@ describe HappyMapper::Item do
|
|
77
77
|
item.typecast(a).should == '21'
|
78
78
|
end
|
79
79
|
end
|
80
|
-
|
80
|
+
|
81
81
|
it "should work with Integers" do
|
82
82
|
item = HappyMapper::Item.new(:foo, Integer)
|
83
83
|
[21, 21.0, '21'].each do |a|
|
84
84
|
item.typecast(a).should == 21
|
85
85
|
end
|
86
86
|
end
|
87
|
-
|
87
|
+
|
88
88
|
it "should work with Floats" do
|
89
89
|
item = HappyMapper::Item.new(:foo, Float)
|
90
90
|
[21, 21.0, '21'].each do |a|
|
91
91
|
item.typecast(a).should == 21.0
|
92
92
|
end
|
93
93
|
end
|
94
|
-
|
94
|
+
|
95
95
|
it "should work with Times" do
|
96
96
|
item = HappyMapper::Item.new(:foo, Time)
|
97
97
|
item.typecast('2000-01-01 01:01:01.123456').should == Time.local(2000, 1, 1, 1, 1, 1, 123456)
|
98
98
|
end
|
99
|
-
|
99
|
+
|
100
100
|
it "should work with Dates" do
|
101
101
|
item = HappyMapper::Item.new(:foo, Date)
|
102
102
|
item.typecast('2000-01-01').should == Date.new(2000, 1, 1)
|
103
103
|
end
|
104
|
-
|
104
|
+
|
105
105
|
it "should work with DateTimes" do
|
106
106
|
item = HappyMapper::Item.new(:foo, DateTime)
|
107
107
|
item.typecast('2000-01-01 00:00:00').should == DateTime.new(2000, 1, 1, 0, 0, 0)
|
108
108
|
end
|
109
|
-
|
109
|
+
|
110
110
|
it "should work with Boolean" do
|
111
111
|
item = HappyMapper::Item.new(:foo, Boolean)
|
112
112
|
item.typecast('false').should == false
|
data/spec/happymapper_spec.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'spec_helper'
|
2
2
|
require 'pp'
|
3
3
|
require 'uri'
|
4
4
|
require 'support/models'
|
@@ -142,7 +142,7 @@ describe HappyMapper do
|
|
142
142
|
first.extended.should == 'ROXML is a Ruby library designed to make it easier for Ruby developers to work with XML. Using simple annotations, it enables Ruby classes to be custom-mapped to XML. ROXML takes care of the marshalling and unmarshalling of mapped attributes so that developers can focus on building first-class Ruby classes.'
|
143
143
|
end
|
144
144
|
|
145
|
-
it "should parse xml elements to ruby
|
145
|
+
it "should parse xml elements to ruby objects" do
|
146
146
|
statuses = Status.parse(fixture_file('statuses.xml'))
|
147
147
|
statuses.size.should == 20
|
148
148
|
first = statuses.first
|
@@ -173,6 +173,13 @@ describe HappyMapper do
|
|
173
173
|
address.country.should == 'Germany'
|
174
174
|
end
|
175
175
|
|
176
|
+
it "should parse xml containing a has many relationship with primitive types" do
|
177
|
+
address = MultiStreetAddress.parse(fixture_file('multi_street_address.xml'), :single => true)
|
178
|
+
address.should_not be_nil
|
179
|
+
address.street_address.first.should == "123 Smith Dr"
|
180
|
+
address.street_address.last.should == "Apt 31"
|
181
|
+
end
|
182
|
+
|
176
183
|
it "should parse xml with default namespace (amazon)" do
|
177
184
|
file_contents = fixture_file('pita.xml')
|
178
185
|
items = PITA::Items.parse(file_contents, :single => true)
|
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ToXMLWithNamespaces
|
4
|
+
|
5
|
+
#
|
6
|
+
# Similar example as the to_xml but this time with namespacing
|
7
|
+
#
|
8
|
+
class Address
|
9
|
+
include HappyMapper
|
10
|
+
|
11
|
+
register_namespace 'address', 'http://www.company.com/address'
|
12
|
+
register_namespace 'country', 'http://www.company.com/country'
|
13
|
+
|
14
|
+
tag 'Address'
|
15
|
+
namespace 'address'
|
16
|
+
|
17
|
+
element :country, 'Country', :tag => 'country', :namespace => 'country'
|
18
|
+
|
19
|
+
|
20
|
+
attribute :location, String
|
21
|
+
|
22
|
+
element :street, String
|
23
|
+
element :postcode, String
|
24
|
+
element :city, String
|
25
|
+
|
26
|
+
element :housenumber, String
|
27
|
+
|
28
|
+
#
|
29
|
+
# to_xml will default to the attr_accessor method and not the attribute,
|
30
|
+
# allowing for that to be overwritten
|
31
|
+
#
|
32
|
+
def housenumber
|
33
|
+
"[#{@housenumber}]"
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Write a empty element even if this is not specified
|
38
|
+
#
|
39
|
+
element :description, String, :state_when_nil => true
|
40
|
+
|
41
|
+
#
|
42
|
+
# Perform the on_save operation when saving
|
43
|
+
#
|
44
|
+
has_one :date_created, Time, :on_save => lambda {|time| DateTime.parse(time).strftime("%T %D") if time }
|
45
|
+
|
46
|
+
#
|
47
|
+
# Write multiple elements and call on_save when saving
|
48
|
+
#
|
49
|
+
has_many :dates_updated, Time, :on_save => lambda {|times|
|
50
|
+
times.compact.map {|time| DateTime.parse(time).strftime("%T %D") } if times }
|
51
|
+
|
52
|
+
#
|
53
|
+
# Class composition
|
54
|
+
#
|
55
|
+
|
56
|
+
def initialize(parameters)
|
57
|
+
parameters.each_pair do |property,value|
|
58
|
+
send("#{property}=",value) if respond_to?("#{property}=")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Country is composed above the in Address class. Here is a demonstration
|
66
|
+
# of how to_xml will handle class composition as well as utilizing the tag
|
67
|
+
# value.
|
68
|
+
#
|
69
|
+
class Country
|
70
|
+
include HappyMapper
|
71
|
+
|
72
|
+
register_namespace 'countryName', 'http://www.company.com/countryName'
|
73
|
+
|
74
|
+
attribute :code, String, :tag => 'countryCode'
|
75
|
+
has_one :name, String, :tag => 'countryName', :namespace => 'countryName'
|
76
|
+
|
77
|
+
def initialize(parameters)
|
78
|
+
parameters.each_pair do |property,value|
|
79
|
+
send("#{property}=",value) if respond_to?("#{property}=")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "#to_xml" do
|
86
|
+
|
87
|
+
context "Address" do
|
88
|
+
|
89
|
+
before(:all) do
|
90
|
+
address = Address.new('street' => 'Mockingbird Lane',
|
91
|
+
'location' => 'Home',
|
92
|
+
'housenumber' => '1313',
|
93
|
+
'postcode' => '98103',
|
94
|
+
'city' => 'Seattle',
|
95
|
+
'country' => Country.new(:name => 'USA', :code => 'us'),
|
96
|
+
'date_created' => '2011-01-01 15:00:00')
|
97
|
+
|
98
|
+
address.dates_updated = ["2011-01-01 16:01:00","2011-01-02 11:30:01"]
|
99
|
+
|
100
|
+
@address_xml = XML::Parser.string(address.to_xml).parse.root
|
101
|
+
end
|
102
|
+
|
103
|
+
{ 'street' => 'Mockingbird Lane',
|
104
|
+
'postcode' => '98103',
|
105
|
+
'city' => 'Seattle' }.each_pair do |property,value|
|
106
|
+
|
107
|
+
it "should have the element '#{property}' with the value '#{value}'" do
|
108
|
+
@address_xml.find("address:#{property}").first.child.to_s.should == value
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should use the result of #housenumber method (not the @housenumber)" do
|
114
|
+
@address_xml.find("address:housenumber").first.child.to_s.should == "[1313]"
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should have the attribute 'location' with the value 'Home'" do
|
118
|
+
@address_xml.find('@location').first.child.to_s.should == "Home"
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should add an empty description element" do
|
122
|
+
@address_xml.find('address:description').first.child.to_s.should == ""
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should call #on_save when saving the time to convert the time" do
|
126
|
+
@address_xml.find('address:date_created').first.child.to_s.should == "15:00:00 01/01/11"
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should handle multiple elements for 'has_many'" do
|
130
|
+
dates_updated = @address_xml.find('address:dates_updated')
|
131
|
+
dates_updated.length.should == 2
|
132
|
+
dates_updated.first.child.to_s.should == "16:01:00 01/01/11"
|
133
|
+
dates_updated.last.child.to_s.should == "11:30:01 01/02/11"
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should write the country code" do
|
137
|
+
@address_xml.find('country:country/@country:countryCode').first.child.to_s.should == "us"
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should write the country name" do
|
141
|
+
@address_xml.find('country:country/countryName:countryName').first.child.to_s.should == "USA"
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module ToXML
|
4
|
+
|
5
|
+
class Address
|
6
|
+
include HappyMapper
|
7
|
+
|
8
|
+
tag 'address'
|
9
|
+
|
10
|
+
attribute :location, String
|
11
|
+
|
12
|
+
element :street, String
|
13
|
+
element :postcode, String
|
14
|
+
element :city, String
|
15
|
+
|
16
|
+
element :housenumber, String
|
17
|
+
|
18
|
+
#
|
19
|
+
# to_xml will default to the attr_accessor method and not the attribute,
|
20
|
+
# allowing for that to be overwritten
|
21
|
+
#
|
22
|
+
def housenumber
|
23
|
+
"[#{@housenumber}]"
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Write a empty element even if this is not specified
|
28
|
+
#
|
29
|
+
element :description, String, :state_when_nil => true
|
30
|
+
|
31
|
+
#
|
32
|
+
# Perform the on_save operation when saving
|
33
|
+
#
|
34
|
+
has_one :date_created, Time, :on_save => lambda {|time| DateTime.parse(time).strftime("%T %D") if time }
|
35
|
+
|
36
|
+
#
|
37
|
+
# Write multiple elements and call on_save when saving
|
38
|
+
#
|
39
|
+
has_many :dates_updated, Time, :on_save => lambda {|times|
|
40
|
+
times.compact.map {|time| DateTime.parse(time).strftime("%T %D") } if times }
|
41
|
+
|
42
|
+
#
|
43
|
+
# Class composition
|
44
|
+
#
|
45
|
+
element :country, 'Country', :tag => 'country'
|
46
|
+
|
47
|
+
def initialize(parameters)
|
48
|
+
parameters.each_pair do |property,value|
|
49
|
+
send("#{property}=",value) if respond_to?("#{property}=")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# Country is composed above the in Address class. Here is a demonstration
|
57
|
+
# of how to_xml will handle class composition as well as utilizing the tag
|
58
|
+
# value.
|
59
|
+
#
|
60
|
+
class Country
|
61
|
+
include HappyMapper
|
62
|
+
|
63
|
+
attribute :code, String, :tag => 'countryCode'
|
64
|
+
has_one :name, String, :tag => 'countryName'
|
65
|
+
|
66
|
+
def initialize(parameters)
|
67
|
+
parameters.each_pair do |property,value|
|
68
|
+
send("#{property}=",value) if respond_to?("#{property}=")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
describe "#to_xml" do
|
75
|
+
|
76
|
+
context "Address" do
|
77
|
+
|
78
|
+
before(:all) do
|
79
|
+
address = Address.new('street' => 'Mockingbird Lane',
|
80
|
+
'location' => 'Home',
|
81
|
+
'housenumber' => '1313',
|
82
|
+
'postcode' => '98103',
|
83
|
+
'city' => 'Seattle',
|
84
|
+
'country' => Country.new(:name => 'USA', :code => 'us'),
|
85
|
+
'date_created' => '2011-01-01 15:00:00')
|
86
|
+
|
87
|
+
address.dates_updated = ["2011-01-01 16:01:00","2011-01-02 11:30:01"]
|
88
|
+
|
89
|
+
@address_xml = XML::Parser.string(address.to_xml).parse.root
|
90
|
+
end
|
91
|
+
|
92
|
+
{ 'street' => 'Mockingbird Lane',
|
93
|
+
'postcode' => '98103',
|
94
|
+
'city' => 'Seattle' }.each_pair do |property,value|
|
95
|
+
|
96
|
+
it "should have the element '#{property}' with the value '#{value}'" do
|
97
|
+
@address_xml.find("#{property}").first.child.to_s.should == value
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should use the result of #housenumber method (not the @housenumber)" do
|
103
|
+
@address_xml.find("housenumber").first.child.to_s.should == "[1313]"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should have the attribute 'location' with the value 'Home'" do
|
107
|
+
@address_xml.find('@location').first.child.to_s.should == "Home"
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should add an empty description element" do
|
111
|
+
@address_xml.find('description').first.child.to_s.should == ""
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should call #on_save when saving the time to convert the time" do
|
115
|
+
@address_xml.find('date_created').first.child.to_s.should == "15:00:00 01/01/11"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should handle multiple elements for 'has_many'" do
|
119
|
+
dates_updated = @address_xml.find('dates_updated')
|
120
|
+
dates_updated.length.should == 2
|
121
|
+
dates_updated.first.child.to_s.should == "16:01:00 01/01/11"
|
122
|
+
dates_updated.last.child.to_s.should == "11:30:01 01/02/11"
|
123
|
+
end
|
124
|
+
|
125
|
+
it "should write the country code" do
|
126
|
+
@address_xml.find('country/@countryCode').first.child.to_s.should == "us"
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should write the country name" do
|
130
|
+
@address_xml.find('country/countryName').first.child.to_s.should == "USA"
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
data/spec/spec_helper.rb
CHANGED
data/spec/support/models.rb
CHANGED
@@ -239,6 +239,18 @@ class Address
|
|
239
239
|
element :country, String
|
240
240
|
end
|
241
241
|
|
242
|
+
class MultiStreetAddress
|
243
|
+
include HappyMapper
|
244
|
+
|
245
|
+
tag 'address'
|
246
|
+
# allow primitive type to be collection
|
247
|
+
has_many :street_address, String, :tag => "streetaddress"
|
248
|
+
element :city, String
|
249
|
+
element :state_or_providence, String, :tag => "stateOfProvidence"
|
250
|
+
element :zip, String
|
251
|
+
element :country, String
|
252
|
+
end
|
253
|
+
|
242
254
|
# for type coercion
|
243
255
|
class ProductGroup < String; end
|
244
256
|
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: happymapper
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
5
6
|
segments:
|
6
7
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.0
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- John Nunemaker
|
@@ -14,37 +15,23 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date:
|
18
|
-
default_executable:
|
18
|
+
date: 2011-09-26 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
|
-
name: libxml-ruby
|
22
|
-
prerelease: false
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
-
requirements:
|
25
|
-
- - ~>
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
segments:
|
28
|
-
- 1
|
29
|
-
- 1
|
30
|
-
- 3
|
31
|
-
version: 1.1.3
|
32
21
|
type: :runtime
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: rspec
|
36
22
|
prerelease: false
|
37
|
-
requirement: &
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
38
25
|
requirements:
|
39
26
|
- - ~>
|
40
27
|
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
41
29
|
segments:
|
42
|
-
-
|
43
|
-
- 3
|
30
|
+
- 2
|
44
31
|
- 0
|
45
|
-
version:
|
46
|
-
|
47
|
-
|
32
|
+
version: "2.0"
|
33
|
+
version_requirements: *id001
|
34
|
+
name: libxml-ruby
|
48
35
|
description:
|
49
36
|
email:
|
50
37
|
- nunemaker@gmail.com
|
@@ -86,13 +73,14 @@ files:
|
|
86
73
|
- spec/happymapper_element_spec.rb
|
87
74
|
- spec/happymapper_item_spec.rb
|
88
75
|
- spec/happymapper_spec.rb
|
76
|
+
- spec/happymapper_to_xml_namespaces_spec.rb
|
77
|
+
- spec/happymapper_to_xml_spec.rb
|
89
78
|
- spec/spec.opts
|
90
79
|
- spec/spec_helper.rb
|
91
80
|
- spec/support/models.rb
|
92
81
|
- License
|
93
82
|
- Rakefile
|
94
83
|
- README.rdoc
|
95
|
-
has_rdoc: true
|
96
84
|
homepage: http://happymapper.rubyforge.org
|
97
85
|
licenses: []
|
98
86
|
|
@@ -102,23 +90,27 @@ rdoc_options: []
|
|
102
90
|
require_paths:
|
103
91
|
- lib
|
104
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
105
94
|
requirements:
|
106
95
|
- - ">="
|
107
96
|
- !ruby/object:Gem::Version
|
97
|
+
hash: 3
|
108
98
|
segments:
|
109
99
|
- 0
|
110
100
|
version: "0"
|
111
101
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
+
none: false
|
112
103
|
requirements:
|
113
104
|
- - ">="
|
114
105
|
- !ruby/object:Gem::Version
|
106
|
+
hash: 3
|
115
107
|
segments:
|
116
108
|
- 0
|
117
109
|
version: "0"
|
118
110
|
requirements: []
|
119
111
|
|
120
112
|
rubyforge_project: happymapper
|
121
|
-
rubygems_version: 1.
|
113
|
+
rubygems_version: 1.8.9
|
122
114
|
signing_key:
|
123
115
|
specification_version: 3
|
124
116
|
summary: object to xml mapping library
|