happymapper 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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