happymapper-swanandp 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.
Files changed (41) hide show
  1. data/License +20 -0
  2. data/README.rdoc +55 -0
  3. data/Rakefile +35 -0
  4. data/examples/amazon.rb +34 -0
  5. data/examples/current_weather.rb +21 -0
  6. data/examples/dashed_elements.rb +20 -0
  7. data/examples/multi_street_address.rb +30 -0
  8. data/examples/post.rb +19 -0
  9. data/examples/twitter.rb +37 -0
  10. data/lib/happymapper.rb +321 -0
  11. data/lib/happymapper/attribute.rb +3 -0
  12. data/lib/happymapper/element.rb +3 -0
  13. data/lib/happymapper/item.rb +179 -0
  14. data/lib/happymapper/version.rb +3 -0
  15. data/spec/fixtures/address.xml +8 -0
  16. data/spec/fixtures/analytics.xml +61 -0
  17. data/spec/fixtures/commit.xml +52 -0
  18. data/spec/fixtures/current_weather.xml +89 -0
  19. data/spec/fixtures/family_tree.xml +7 -0
  20. data/spec/fixtures/multi_street_address.xml +9 -0
  21. data/spec/fixtures/multiple_namespaces.xml +170 -0
  22. data/spec/fixtures/nested_namespaces.xml +17 -0
  23. data/spec/fixtures/notes.xml +9 -0
  24. data/spec/fixtures/pita.xml +133 -0
  25. data/spec/fixtures/posts.xml +23 -0
  26. data/spec/fixtures/product_default_namespace.xml +10 -0
  27. data/spec/fixtures/product_no_namespace.xml +10 -0
  28. data/spec/fixtures/product_single_namespace.xml +10 -0
  29. data/spec/fixtures/radar.xml +21 -0
  30. data/spec/fixtures/raw.xml +9 -0
  31. data/spec/fixtures/statuses.xml +422 -0
  32. data/spec/happymapper_attribute_spec.rb +17 -0
  33. data/spec/happymapper_element_spec.rb +17 -0
  34. data/spec/happymapper_item_spec.rb +115 -0
  35. data/spec/happymapper_spec.rb +404 -0
  36. data/spec/happymapper_to_xml_namespaces_spec.rb +149 -0
  37. data/spec/happymapper_to_xml_spec.rb +138 -0
  38. data/spec/spec.opts +1 -0
  39. data/spec/spec_helper.rb +9 -0
  40. data/spec/support/models.rb +323 -0
  41. metadata +121 -0
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe HappyMapper::Attribute do
4
+ describe "initialization" do
5
+ before do
6
+ @attr = HappyMapper::Attribute.new(:foo, String)
7
+ end
8
+
9
+ it 'should know that it is an attribute' do
10
+ @attr.attribute?.should be_true
11
+ end
12
+
13
+ it 'should know that it is NOT an element' do
14
+ @attr.element?.should be_false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe HappyMapper::Element do
4
+ describe "initialization" do
5
+ before do
6
+ @attr = HappyMapper::Element.new(:foo, String)
7
+ end
8
+
9
+ it 'should know that it is an element' do
10
+ @attr.element?.should be_true
11
+ end
12
+
13
+ it 'should know that it is NOT an attribute' do
14
+ @attr.attribute?.should be_false
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,115 @@
1
+ require 'spec_helper'
2
+
3
+ module Foo
4
+ class Bar; end
5
+ end
6
+
7
+ describe HappyMapper::Item do
8
+
9
+ describe "new instance" do
10
+ before do
11
+ @item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
12
+ end
13
+
14
+ it "should accept a name" do
15
+ @item.name.should == 'foo'
16
+ end
17
+
18
+ it 'should accept a type' do
19
+ @item.type.should == String
20
+ end
21
+
22
+ it 'should accept :tag as an option' do
23
+ @item.tag.should == 'foobar'
24
+ end
25
+
26
+ it "should have a method_name" do
27
+ @item.method_name.should == 'foo'
28
+ end
29
+ end
30
+
31
+ describe "#constant" do
32
+ it "should just use type if constant" do
33
+ item = HappyMapper::Item.new(:foo, String)
34
+ item.constant.should == String
35
+ end
36
+
37
+ it "should convert string type to constant" do
38
+ item = HappyMapper::Item.new(:foo, 'String')
39
+ item.constant.should == String
40
+ end
41
+
42
+ it "should convert string with :: to constant" do
43
+ item = HappyMapper::Item.new(:foo, 'Foo::Bar')
44
+ item.constant.should == Foo::Bar
45
+ end
46
+ end
47
+
48
+ describe "#method_name" do
49
+ it "should convert dashes to underscores" do
50
+ item = HappyMapper::Item.new(:'foo-bar', String, :tag => 'foobar')
51
+ item.method_name.should == 'foo_bar'
52
+ end
53
+ end
54
+
55
+ describe "#xpath" do
56
+ it "should default to tag" do
57
+ item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
58
+ item.xpath.should == 'foobar'
59
+ end
60
+
61
+ it "should prepend with .// if options[:deep] true" do
62
+ item = HappyMapper::Item.new(:foo, String, :tag => 'foobar', :deep => true)
63
+ item.xpath.should == './/foobar'
64
+ end
65
+
66
+ it "should prepend namespace if namespace exists" do
67
+ item = HappyMapper::Item.new(:foo, String, :tag => 'foobar')
68
+ item.namespace = 'http://example.com'
69
+ item.xpath.should == 'happymapper:foobar'
70
+ end
71
+ end
72
+
73
+ describe "typecasting" do
74
+ it "should work with Strings" do
75
+ item = HappyMapper::Item.new(:foo, String)
76
+ [21, '21'].each do |a|
77
+ item.typecast(a).should == '21'
78
+ end
79
+ end
80
+
81
+ it "should work with Integers" do
82
+ item = HappyMapper::Item.new(:foo, Integer)
83
+ [21, 21.0, '21'].each do |a|
84
+ item.typecast(a).should == 21
85
+ end
86
+ end
87
+
88
+ it "should work with Floats" do
89
+ item = HappyMapper::Item.new(:foo, Float)
90
+ [21, 21.0, '21'].each do |a|
91
+ item.typecast(a).should == 21.0
92
+ end
93
+ end
94
+
95
+ it "should work with Times" do
96
+ item = HappyMapper::Item.new(:foo, Time)
97
+ item.typecast('2000-01-01 01:01:01.123456').should == Time.local(2000, 1, 1, 1, 1, 1, 123456)
98
+ end
99
+
100
+ it "should work with Dates" do
101
+ item = HappyMapper::Item.new(:foo, Date)
102
+ item.typecast('2000-01-01').should == Date.new(2000, 1, 1)
103
+ end
104
+
105
+ it "should work with DateTimes" do
106
+ item = HappyMapper::Item.new(:foo, DateTime)
107
+ item.typecast('2000-01-01 00:00:00').should == DateTime.new(2000, 1, 1, 0, 0, 0)
108
+ end
109
+
110
+ it "should work with Boolean" do
111
+ item = HappyMapper::Item.new(:foo, Boolean)
112
+ item.typecast('false').should == false
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,404 @@
1
+ require 'spec_helper'
2
+ require 'pp'
3
+ require 'uri'
4
+ require 'support/models'
5
+
6
+ describe HappyMapper do
7
+
8
+ describe "being included into another class" do
9
+ before do
10
+ @klass = Class.new do
11
+ include HappyMapper
12
+
13
+ def self.to_s
14
+ 'Foo'
15
+ end
16
+ end
17
+ end
18
+
19
+ it "should set attributes to an array" do
20
+ @klass.attributes.should == []
21
+ end
22
+
23
+ it "should set @elements to a hash" do
24
+ @klass.elements.should == []
25
+ end
26
+
27
+ it "should allow adding an attribute" do
28
+ lambda {
29
+ @klass.attribute :name, String
30
+ }.should change(@klass, :attributes)
31
+ end
32
+
33
+ it "should allow adding an attribute containing a dash" do
34
+ lambda {
35
+ @klass.attribute :'bar-baz', String
36
+ }.should change(@klass, :attributes)
37
+ end
38
+
39
+ it "should be able to get all attributes in array" do
40
+ @klass.attribute :name, String
41
+ @klass.attributes.size.should == 1
42
+ end
43
+
44
+ it "should allow adding an element" do
45
+ lambda {
46
+ @klass.element :name, String
47
+ }.should change(@klass, :elements)
48
+ end
49
+
50
+ it "should allow adding an element containing a dash" do
51
+ lambda {
52
+ @klass.element :'bar-baz', String
53
+ }.should change(@klass, :elements)
54
+
55
+ end
56
+
57
+ it "should be able to get all elements in array" do
58
+ @klass.element(:name, String)
59
+ @klass.elements.size.should == 1
60
+ end
61
+
62
+ it "should allow has one association" do
63
+ @klass.has_one(:user, User)
64
+ element = @klass.elements.first
65
+ element.name.should == 'user'
66
+ element.type.should == User
67
+ element.options[:single] = true
68
+ end
69
+
70
+ it "should allow has many association" do
71
+ @klass.has_many(:users, User)
72
+ element = @klass.elements.first
73
+ element.name.should == 'users'
74
+ element.type.should == User
75
+ element.options[:single] = false
76
+ end
77
+
78
+ it "has after_parse callbacks" do
79
+ call1 = lambda { |doc| puts doc.inspect }
80
+ call2 = lambda { |doc| puts doc.inspect }
81
+ @klass.after_parse(&call1)
82
+ @klass.after_parse(&call2)
83
+ @klass.after_parse_callbacks.should == [call1, call2]
84
+ end
85
+
86
+ it "should default tag name to lowercase class" do
87
+ @klass.tag_name.should == 'foo'
88
+ end
89
+
90
+ it "should default tag name of class in modules to the last constant lowercase" do
91
+ module Bar; class Baz; include HappyMapper; end; end
92
+ Bar::Baz.tag_name.should == 'baz'
93
+ end
94
+
95
+ it "should allow setting tag name" do
96
+ @klass.tag('FooBar')
97
+ @klass.tag_name.should == 'FooBar'
98
+ end
99
+
100
+ it "should allow setting a namespace" do
101
+ @klass.namespace(namespace = "foo")
102
+ @klass.namespace.should == namespace
103
+ end
104
+
105
+ it "should provide #parse" do
106
+ @klass.should respond_to(:parse)
107
+ end
108
+ end
109
+
110
+ describe "#attributes" do
111
+ it "should only return attributes for the current class" do
112
+ Post.attributes.size.should == 7
113
+ Status.attributes.size.should == 0
114
+ end
115
+ end
116
+
117
+ describe "#elements" do
118
+ it "should only return elements for the current class" do
119
+ Post.elements.size.should == 0
120
+ Status.elements.size.should == 10
121
+ end
122
+ end
123
+
124
+ describe "running after_parse callbacks" do
125
+ it "works" do
126
+ user = Status.parse(fixture_file('statuses.xml')).first.user
127
+ user.after_parse_called.should be_true
128
+ user.after_parse2_called.should be_true
129
+ end
130
+ end
131
+
132
+ it "should parse xml attributes into ruby objects" do
133
+ posts = Post.parse(fixture_file('posts.xml'))
134
+ posts.size.should == 20
135
+ first = posts.first
136
+ first.href.should == 'http://roxml.rubyforge.org/'
137
+ first.hash.should == '19bba2ab667be03a19f67fb67dc56917'
138
+ first.description.should == 'ROXML - Ruby Object to XML Mapping Library'
139
+ first.tag.should == 'ruby xml gems mapping'
140
+ first.time.should == Time.utc(2008, 8, 9, 5, 24, 20)
141
+ first.others.should == 56
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
+ end
144
+
145
+ it "should parse xml elements to ruby objects" do
146
+ statuses = Status.parse(fixture_file('statuses.xml'))
147
+ statuses.size.should == 20
148
+ first = statuses.first
149
+ first.id.should == 882281424
150
+ first.created_at.should == Time.utc(2008, 8, 9, 5, 38, 12)
151
+ first.source.should == 'web'
152
+ first.truncated.should be_false
153
+ first.in_reply_to_status_id.should == 1234
154
+ first.in_reply_to_user_id.should == 12345
155
+ first.favorited.should be_false
156
+ first.user.id.should == 4243
157
+ first.user.name.should == 'John Nunemaker'
158
+ first.user.screen_name.should == 'jnunemaker'
159
+ first.user.location.should == 'Mishawaka, IN, US'
160
+ first.user.description.should == 'Loves his wife, ruby, notre dame football and iu basketball'
161
+ first.user.profile_image_url.should == 'http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg'
162
+ first.user.url.should == 'http://addictedtonew.com'
163
+ first.user.protected.should be_false
164
+ first.user.followers_count.should == 486
165
+ end
166
+
167
+ it "should parse xml containing the desired element as root node" do
168
+ address = Address.parse(fixture_file('address.xml'), :single => true)
169
+ address.street.should == 'Milchstrasse'
170
+ address.postcode.should == '26131'
171
+ address.housenumber.should == '23'
172
+ address.city.should == 'Oldenburg'
173
+ address.country.should == 'Germany'
174
+ end
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
+
183
+ it "should parse xml with default namespace (amazon)" do
184
+ file_contents = fixture_file('pita.xml')
185
+ items = PITA::Items.parse(file_contents, :single => true)
186
+ items.total_results.should == 22
187
+ items.total_pages.should == 3
188
+ first = items.items[0]
189
+ second = items.items[1]
190
+ first.asin.should == '0321480791'
191
+ first.point.should == '38.5351715088 -121.7948684692'
192
+ first.detail_page_url.should be_a_kind_of(URI)
193
+ first.detail_page_url.to_s.should == 'http://www.amazon.com/gp/redirect.html%3FASIN=0321480791%26tag=ws%26lcode=xm2%26cID=2025%26ccmID=165953%26location=/o/ASIN/0321480791%253FSubscriptionId=dontbeaswoosh'
194
+ first.manufacturer.should == 'Addison-Wesley Professional'
195
+ first.product_group.should == '<ProductGroup>Book</ProductGroup>'
196
+ second.asin.should == '047022388X'
197
+ second.manufacturer.should == 'Wrox'
198
+ end
199
+
200
+ it "should parse xml that has attributes of elements" do
201
+ items = CurrentWeather.parse(fixture_file('current_weather.xml'))
202
+ first = items[0]
203
+ first.temperature.should == 51
204
+ first.feels_like.should == 51
205
+ first.current_condition.should == 'Sunny'
206
+ first.current_condition.icon.should == 'http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif'
207
+ end
208
+
209
+ it "should parse xml with nested elements" do
210
+ radars = Radar.parse(fixture_file('radar.xml'))
211
+ first = radars[0]
212
+ first.places.size.should == 1
213
+ first.places[0].name.should == 'Store'
214
+ second = radars[1]
215
+ second.places.size.should == 0
216
+ third = radars[2]
217
+ third.places.size.should == 2
218
+ third.places[0].name.should == 'Work'
219
+ third.places[1].name.should == 'Home'
220
+ end
221
+
222
+ it "should parse xml that has elements with dashes" do
223
+ commit = GitHub::Commit.parse(fixture_file('commit.xml'))
224
+ commit.message.should == "move commands.rb and helpers.rb into commands/ dir"
225
+ commit.url.should == "http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
226
+ commit.id.should == "c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
227
+ commit.committed_date.should == Date.parse("2008-03-02T16:45:41-08:00")
228
+ commit.tree.should == "28a1a1ca3e663d35ba8bf07d3f1781af71359b76"
229
+ end
230
+
231
+ it "should parse xml with no namespace" do
232
+ product = Product.parse(fixture_file('product_no_namespace.xml'), :single => true)
233
+ product.title.should == "A Title"
234
+ product.feature_bullets.bug.should == 'This is a bug'
235
+ product.feature_bullets.features.size.should == 2
236
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
237
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
238
+ end
239
+
240
+ it "should parse xml with default namespace" do
241
+ product = Product.parse(fixture_file('product_default_namespace.xml'), :single => true)
242
+ product.title.should == "A Title"
243
+ product.feature_bullets.bug.should == 'This is a bug'
244
+ product.feature_bullets.features.size.should == 2
245
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
246
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
247
+ end
248
+
249
+ it "should parse xml with single namespace" do
250
+ product = Product.parse(fixture_file('product_single_namespace.xml'), :single => true)
251
+ product.title.should == "A Title"
252
+ product.feature_bullets.bug.should == 'This is a bug'
253
+ product.feature_bullets.features.size.should == 2
254
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
255
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
256
+ end
257
+
258
+ it "should parse xml with multiple namespaces" do
259
+ track = FedEx::TrackReply.parse(fixture_file('multiple_namespaces.xml'))
260
+ track.highest_severity.should == 'SUCCESS'
261
+ track.more_data.should be_false
262
+ notification = track.notifications.first
263
+ notification.code.should == 0
264
+ notification.localized_message.should == 'Request was successfully processed.'
265
+ notification.message.should == 'Request was successfully processed.'
266
+ notification.severity.should == 'SUCCESS'
267
+ notification.source.should == 'trck'
268
+ detail = track.trackdetails.first
269
+ detail.carrier_code.should == 'FDXG'
270
+ detail.est_delivery.should == '2009-01-02T00:00:00'
271
+ detail.service_info.should == 'Ground-Package Returns Program-Domestic'
272
+ detail.status_code.should == 'OD'
273
+ detail.status_desc.should == 'On FedEx vehicle for delivery'
274
+ detail.tracking_number.should == '9611018034267800045212'
275
+ detail.weight.units.should == 'LB'
276
+ detail.weight.value.should == 2
277
+ events = detail.events
278
+ events.size.should == 10
279
+ first_event = events[0]
280
+ first_event.eventdescription.should == 'On FedEx vehicle for delivery'
281
+ first_event.eventtype.should == 'OD'
282
+ first_event.timestamp.should == '2009-01-02T06:00:00'
283
+ first_event.address.city.should == 'WICHITA'
284
+ first_event.address.countrycode.should == 'US'
285
+ first_event.address.residential.should be_false
286
+ first_event.address.state.should == 'KS'
287
+ first_event.address.zip.should == '67226'
288
+ last_event = events[-1]
289
+ last_event.eventdescription.should == 'In FedEx possession'
290
+ last_event.eventtype.should == 'IP'
291
+ last_event.timestamp.should == '2008-12-27T09:40:00'
292
+ last_event.address.city.should == 'LONGWOOD'
293
+ last_event.address.countrycode.should == 'US'
294
+ last_event.address.residential.should be_false
295
+ last_event.address.state.should == 'FL'
296
+ last_event.address.zip.should == '327506398'
297
+ track.tran_detail.cust_tran_id.should == '20090102-111321'
298
+ end
299
+
300
+ it "should be able to parse from a node's content " do
301
+ notes = Backpack::Note.parse(fixture_file('notes.xml'))
302
+ notes.size.should == 2
303
+
304
+ note = notes[0]
305
+ note.id.should == 1132
306
+ note.title.should == 'My world!'
307
+ note.body.should include("It's a pretty place")
308
+
309
+ note = notes[1]
310
+ note.id.should == 1133
311
+ note.title.should == 'Your world!'
312
+ note.body.should include("Also pretty")
313
+ end
314
+
315
+ it "should be able to parse google analytics api xml" do
316
+ data = Analytics::Feed.parse(fixture_file('analytics.xml'))
317
+ data.id.should == 'http://www.google.com/analytics/feeds/accounts/nunemaker@gmail.com'
318
+ data.entries.size.should == 4
319
+
320
+ entry = data.entries[0]
321
+ entry.title.should == 'addictedtonew.com'
322
+ entry.properties.size.should == 4
323
+
324
+ property = entry.properties[0]
325
+ property.name.should == 'ga:accountId'
326
+ property.value.should == '85301'
327
+ end
328
+
329
+ it "should allow instantiating with a string" do
330
+ module StringFoo
331
+ class Bar
332
+ include HappyMapper
333
+ has_many :things, 'StringFoo::Thing'
334
+ end
335
+
336
+ class Thing
337
+ include HappyMapper
338
+ end
339
+ end
340
+ end
341
+
342
+ it 'should be able to preserve raw contents of a node' do
343
+ address = RawXml::Address.parse(fixture_file('raw.xml'), :single => true)
344
+ address.custom_stuff.body.should == '<foo>F</foo><bar>B</bar>'
345
+ end
346
+
347
+ xit "should parse family search xml" do
348
+ tree = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
349
+ tree.version.should == '1.0.20071213.942'
350
+ tree.status_message.should == 'OK'
351
+ tree.status_code.should == '200'
352
+ # tree.people.size.should == 1
353
+ # tree.people.first.version.should == '1199378491000'
354
+ # tree.people.first.modified.should == Time.utc(2008, 1, 3, 16, 41, 31) # 2008-01-03T09:41:31-07:00
355
+ # tree.people.first.id.should == 'KWQS-BBQ'
356
+ end
357
+
358
+
359
+ describe 'nested elements with namespaces' do
360
+ module Namespaces
361
+ class Info
362
+ include HappyMapper
363
+ namespace 'http://schemas.google.com/analytics/2009'
364
+ element :category, String
365
+ end
366
+
367
+ class Alert
368
+ include HappyMapper
369
+ namespace 'http://schemas.google.com/analytics/2009'
370
+
371
+ element :identifier, String
372
+ element :severity, String, :namespace => false
373
+ has_one :info, Info
374
+ end
375
+ class Distribution
376
+ include HappyMapper
377
+
378
+ tag 'EDXLDistribution'
379
+ has_one :alert, Alert
380
+ end
381
+ end
382
+
383
+ def mapping
384
+ @mapping ||= Namespaces::Distribution.parse(fixture_file('nested_namespaces.xml'))
385
+ end
386
+
387
+ it "should parse elements with inline namespace" do
388
+ lambda { mapping }.should_not raise_error
389
+ end
390
+
391
+ it "should map elements with inline namespace" do
392
+ mapping.alert.identifier.should == 'CDC-2006-183'
393
+ end
394
+
395
+ it "should map sub elements of with nested namespace" do
396
+ mapping.alert.info.category.should == 'Health'
397
+ end
398
+
399
+ it "should map elements without a namespace" do
400
+ mapping.alert.severity.should == 'Severe'
401
+ end
402
+ end
403
+
404
+ end