happymapper 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,7 +2,7 @@
2
2
 
3
3
  == DESCRIPTION:
4
4
 
5
- Object to xml mapping library. I have included examples to help get you going. The specs should also point you in the right direction.
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
@@ -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 :state_or_providence, String, :tag => "stateOfProvidence"
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}"
@@ -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
- attr.from_xml_node(n, namespace))
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
- elem.from_xml_node(n, namespace))
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'
@@ -1,3 +1,3 @@
1
1
  module HappyMapper
2
- Version = '0.3.2'
2
+ Version = '0.4.0'
3
3
  end
@@ -3,7 +3,7 @@
3
3
  <streetaddress>123 Smith Dr</streetaddress>
4
4
  <streetaddress>Apt 31</streetaddress>
5
5
  <city>Anytown</city>
6
- <stateOrProvince>ST<stateOrProvince>
6
+ <stateOrProvince>ST</stateOrProvince>
7
7
  <zip>12345</zip>
8
8
  <country>USA</country>
9
9
  </address>
@@ -1,15 +1,15 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
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 File.dirname(__FILE__) + '/spec_helper.rb'
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 File.dirname(__FILE__) + '/spec_helper.rb'
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
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/spec_helper.rb'
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 objcts" do
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
@@ -1,3 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
1
4
  require 'spec'
2
5
  require File.expand_path('../../lib/happymapper', __FILE__)
3
6
 
@@ -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
- prerelease: false
4
+ hash: 15
5
+ prerelease:
5
6
  segments:
6
7
  - 0
7
- - 3
8
- - 2
9
- version: 0.3.2
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: 2010-07-04 00:00:00 -04:00
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: &id002 !ruby/object:Gem::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
- - 1
43
- - 3
30
+ - 2
44
31
  - 0
45
- version: 1.3.0
46
- type: :development
47
- version_requirements: *id002
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.3.6
113
+ rubygems_version: 1.8.9
122
114
  signing_key:
123
115
  specification_version: 3
124
116
  summary: object to xml mapping library