kdonovan-happymapper 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,694 @@
1
+ require File.dirname(__FILE__) + '/spec_helper.rb'
2
+ require File.dirname(__FILE__) + '/xml_helper.rb'
3
+ require 'pp'
4
+ require 'uri'
5
+
6
+ class Address
7
+ include HappyMapper
8
+
9
+ tag 'address'
10
+ element :street, String
11
+ element :postcode, String
12
+ element :housenumber, String
13
+ element :city, String
14
+ element :country, String
15
+ end
16
+
17
+ class Feature
18
+ include HappyMapper
19
+ element :name, String, :tag => '.|.//text()'
20
+ end
21
+
22
+ class FeatureBullet
23
+ include HappyMapper
24
+
25
+ tag 'features_bullets'
26
+ has_many :features, Feature
27
+ element :bug, String
28
+ end
29
+
30
+ class Product
31
+ include HappyMapper
32
+
33
+ element :title, String
34
+ has_one :feature_bullets, FeatureBullet
35
+ has_one :address, Address
36
+ end
37
+
38
+ module FamilySearch
39
+ class AlternateIds
40
+ include HappyMapper
41
+
42
+ namespace 'fsapi-v1' => 'http://api.familysearch.org/v1'
43
+ tag 'alternateIds'
44
+ has_many :ids, String, :tag => 'id'
45
+ end
46
+
47
+ class Information
48
+ include HappyMapper
49
+
50
+ namespace 'fsapi-v1' => 'http://api.familysearch.org/v1'
51
+ has_one :alternateIds, AlternateIds
52
+ element :gender, String
53
+ element :living, Boolean
54
+ end
55
+
56
+ class Person
57
+ include HappyMapper
58
+
59
+ namespace_url 'http://api.familysearch.org/familytree/v1'
60
+ attribute :version, String
61
+ attribute :modified, Time
62
+ attribute :id, String
63
+ has_one :information, Information
64
+ end
65
+
66
+ class Persons
67
+ include HappyMapper
68
+
69
+ namespace_url 'http://api.familysearch.org/familytree/v1'
70
+ has_many :person, Person
71
+ end
72
+
73
+ class FamilyTree
74
+ include HappyMapper
75
+
76
+ tag 'familytree'
77
+ namespace_url 'http://api.familysearch.org/familytree/v1'
78
+ attribute :version, String
79
+ attribute :status_message, String, :tag => 'statusMessage'
80
+ attribute :status_code, String, :tag => 'statusCode'
81
+ has_one :persons, Persons
82
+ end
83
+ end
84
+
85
+ module FedEx
86
+ class Address
87
+ include HappyMapper
88
+
89
+ tag 'Address'
90
+ namespace 'v2'
91
+ element :city, String, :tag => 'City'
92
+ element :state, String, :tag => 'StateOrProvinceCode'
93
+ element :zip, String, :tag => 'PostalCode'
94
+ element :countrycode, String, :tag => 'CountryCode'
95
+ element :residential, Boolean, :tag => 'Residential'
96
+ end
97
+
98
+ class Event
99
+ include HappyMapper
100
+
101
+ tag 'Events'
102
+ namespace 'v2'
103
+ element :timestamp, String, :tag => 'Timestamp'
104
+ element :eventtype, String, :tag => 'EventType'
105
+ element :eventdescription, String, :tag => 'EventDescription'
106
+ has_one :address, Address
107
+ end
108
+
109
+ class PackageWeight
110
+ include HappyMapper
111
+
112
+ tag 'PackageWeight'
113
+ namespace 'v2'
114
+ element :units, String, :tag => 'Units'
115
+ element :value, Integer, :tag => 'Value'
116
+ end
117
+
118
+ class TrackDetails
119
+ include HappyMapper
120
+
121
+ tag 'TrackDetails'
122
+ namespace 'v2'
123
+ element :tracking_number, String, :tag => 'TrackingNumber'
124
+ element :status_code, String, :tag => 'StatusCode'
125
+ element :status_desc, String, :tag => 'StatusDescription'
126
+ element :carrier_code, String, :tag => 'CarrierCode'
127
+ element :service_info, String, :tag => 'ServiceInfo'
128
+ has_one :weight, PackageWeight, :tag => 'PackageWeight'
129
+ element :est_delivery, String, :tag => 'EstimatedDeliveryTimestamp'
130
+ has_many :events, Event
131
+ end
132
+
133
+ class Notification
134
+ include HappyMapper
135
+
136
+ tag 'Notifications'
137
+ namespace 'v2'
138
+ element :severity, String, :tag => 'Severity'
139
+ element :source, String, :tag => 'Source'
140
+ element :code, Integer, :tag => 'Code'
141
+ element :message, String, :tag => 'Message'
142
+ element :localized_message, String, :tag => 'LocalizedMessage'
143
+ end
144
+
145
+ class TransactionDetail
146
+ include HappyMapper
147
+
148
+ tag 'TransactionDetail'
149
+ namespace 'v2'
150
+ element :cust_tran_id, String, :tag => 'CustomerTransactionId'
151
+ end
152
+
153
+ class TrackReply
154
+ include HappyMapper
155
+
156
+ tag 'TrackReply'
157
+ namespace 'v2'
158
+ element :highest_severity, String, :tag => 'HighestSeverity'
159
+ element :more_data, Boolean, :tag => 'MoreData'
160
+ has_many :notifications, Notification, :tag => 'Notifications'
161
+ has_many :trackdetails, TrackDetails, :tag => 'TrackDetails'
162
+ has_one :tran_detail, TransactionDetail, :tab => 'TransactionDetail'
163
+ end
164
+ end
165
+
166
+ class Place
167
+ include HappyMapper
168
+ element :name, String
169
+ end
170
+
171
+ class Radar
172
+ include HappyMapper
173
+ # places needs to be deep because it lives out in places/place
174
+ has_many :places, Place, :deep => true
175
+ end
176
+
177
+ class Post
178
+ include HappyMapper
179
+
180
+ attribute :href, String
181
+ attribute :hash, String
182
+ attribute :description, String
183
+ attribute :tag, String
184
+ attribute :time, Time
185
+ attribute :others, Integer
186
+ attribute :extended, String
187
+ end
188
+
189
+ class Posts
190
+ include HappyMapper
191
+
192
+ attribute :user, String
193
+ attribute :tag, String
194
+ has_many :post, Post
195
+ end
196
+
197
+ class User
198
+ include HappyMapper
199
+
200
+ element :id, Integer
201
+ element :name, String
202
+ element :screen_name, String
203
+ element :location, String
204
+ element :description, String
205
+ element :profile_image_url, String
206
+ element :url, String
207
+ element :protected, Boolean
208
+ element :followers_count, Integer
209
+ end
210
+
211
+ class Status
212
+ include HappyMapper
213
+
214
+ element :id, Integer
215
+ element :text, String
216
+ element :created_at, Time
217
+ element :source, String
218
+ element :truncated, Boolean
219
+ element :in_reply_to_status_id, Integer
220
+ element :in_reply_to_user_id, Integer
221
+ element :favorited, Boolean
222
+ element :non_existent, String, :tag => 'dummy', :namespace => 'fake'
223
+ has_one :user, User
224
+ end
225
+
226
+ class CurrentWeather
227
+ include HappyMapper
228
+
229
+ tag 'ob'
230
+ namespace 'aws'
231
+ element :temperature, Integer, :tag => 'temp'
232
+ element :feels_like, Integer, :tag => 'feels-like'
233
+ element :current_condition, String, :tag => 'current-condition', :attributes => {:icon => String}
234
+ end
235
+
236
+ # for type coercion
237
+ class ProductGroup < String; end
238
+
239
+ module PITA
240
+ class Item
241
+ include HappyMapper
242
+
243
+ tag 'Item' # if you put class in module you need tag
244
+ element :asin, String, :tag => 'ASIN'
245
+ element :detail_page_url, URI, :tag => 'DetailPageURL', :parser => :parse
246
+ element :manufacturer, String, :tag => 'Manufacturer', :deep => true
247
+ element :point, String, :tag => 'point', :namespace => 'georss'
248
+ element :product_group, ProductGroup, :tag => 'ProductGroup', :deep => true, :parser => :new, :raw => true
249
+ end
250
+
251
+ class Items
252
+ include HappyMapper
253
+
254
+ tag 'Items' # if you put class in module you need tag
255
+ element :total_results, Integer, :tag => 'TotalResults'
256
+ element :total_pages, Integer, :tag => 'TotalPages'
257
+ has_many :items, Item
258
+ end
259
+ end
260
+
261
+ module GitHub
262
+ class Commit
263
+ include HappyMapper
264
+
265
+ tag "commit"
266
+ element :url, String
267
+ element :tree, String
268
+ element :message, String
269
+ element :id, String
270
+ element :'committed-date', Date
271
+ end
272
+ end
273
+
274
+ describe HappyMapper do
275
+
276
+ describe "being included into another class" do
277
+ before do
278
+ Foo.instance_variable_set("@attributes", {})
279
+ Foo.instance_variable_set("@elements", {})
280
+ end
281
+ class Foo; include HappyMapper end
282
+
283
+ it "should set attributes to an array" do
284
+ Foo.attributes.should == []
285
+ end
286
+
287
+ it "should set @elements to a hash" do
288
+ Foo.elements.should == []
289
+ end
290
+
291
+ it "should allow adding an attribute" do
292
+ lambda {
293
+ Foo.attribute :name, String
294
+ }.should change(Foo, :attributes)
295
+ end
296
+
297
+ it "should allow adding an attribute containing a dash" do
298
+ lambda {
299
+ Foo.attribute :'bar-baz', String
300
+ }.should change(Foo, :attributes)
301
+ end
302
+
303
+ it "should be able to get all attributes in array" do
304
+ Foo.attribute :name, String
305
+ Foo.attributes.size.should == 1
306
+ end
307
+
308
+ it "should allow adding an element" do
309
+ lambda {
310
+ Foo.element :name, String
311
+ }.should change(Foo, :elements)
312
+ end
313
+
314
+ it "should allow adding an element containing a dash" do
315
+ lambda {
316
+ Foo.element :'bar-baz', String
317
+ }.should change(Foo, :elements)
318
+
319
+ end
320
+
321
+ it "should be able to get all elements in array" do
322
+ Foo.element(:name, String)
323
+ Foo.elements.size.should == 1
324
+ end
325
+
326
+ it "should allow has one association" do
327
+ Foo.has_one(:user, User)
328
+ element = Foo.elements.first
329
+ element.name.should == 'user'
330
+ element.type.should == User
331
+ element.options[:single] = true
332
+ end
333
+
334
+ it "should allow has many association" do
335
+ Foo.has_many(:users, User)
336
+ element = Foo.elements.first
337
+ element.name.should == 'users'
338
+ element.type.should == User
339
+ element.options[:single] = false
340
+ end
341
+
342
+ it "should default tag name to lowercase class" do
343
+ Foo.tag_name.should == 'foo'
344
+ end
345
+
346
+ it "should default tag name of class in modules to the last constant lowercase" do
347
+ module Bar; class Baz; include HappyMapper; end; end
348
+ Bar::Baz.tag_name.should == 'baz'
349
+ end
350
+
351
+ it "should allow setting tag name" do
352
+ Foo.tag('FooBar')
353
+ Foo.tag_name.should == 'FooBar'
354
+ end
355
+
356
+ it "should allow setting a namespace" do
357
+ Foo.namespace(namespace = "foo")
358
+ Foo.namespace.should == namespace
359
+ end
360
+
361
+ # This is important for allowing serializing of elements with namespaces
362
+ it "should allow setting a full namespace (prefix and url) via a hash" do
363
+ Foo.namespace(namespace = {'example-v1' => 'http://example.com/v1'})
364
+ Foo.namespace.should == 'example-v1'
365
+ Foo.namespace_url.should == 'http://example.com/v1'
366
+ end
367
+
368
+ # Prefixes might change for a given namespace. It is safer to namespace by url.
369
+ it "should allow setting a namespace url" do
370
+ Foo.namespace_url(url = 'http://example.com/namespace')
371
+ Foo.namespace_url.should == url
372
+ end
373
+
374
+ it "should provide #parse" do
375
+ Foo.should respond_to(:parse)
376
+ end
377
+ end
378
+
379
+ describe "#attributes" do
380
+ it "should only return attributes for the current class" do
381
+ Post.attributes.size.should == 7
382
+ Status.attributes.size.should == 0
383
+ end
384
+ end
385
+
386
+ describe "#elements" do
387
+ it "should only return elements for the current class" do
388
+ Post.elements.size.should == 0
389
+ Status.elements.size.should == 10
390
+ end
391
+ end
392
+
393
+ it "should parse xml attributes into ruby objects" do
394
+ posts = Post.parse(fixture_file('posts.xml'))
395
+ posts.size.should == 20
396
+ first = posts.first
397
+ first.href.should == 'http://roxml.rubyforge.org/'
398
+ first.hash.should == '19bba2ab667be03a19f67fb67dc56917'
399
+ first.description.should == 'ROXML - Ruby Object to XML Mapping Library'
400
+ first.tag.should == 'ruby xml gems mapping'
401
+ first.time.should == Time.utc(2008, 8, 9, 5, 24, 20)
402
+ first.others.should == 56
403
+ 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.'
404
+ end
405
+
406
+ it "should parse xml elements to ruby objcts" do
407
+ statuses = Status.parse(fixture_file('statuses.xml'))
408
+ statuses.size.should == 20
409
+ first = statuses.first
410
+ first.id.should == 882281424
411
+ first.created_at.should == Time.utc(2008, 8, 9, 5, 38, 12)
412
+ first.source.should == 'web'
413
+ first.truncated.should be_false
414
+ first.in_reply_to_status_id.should == 1234
415
+ first.in_reply_to_user_id.should == 12345
416
+ first.favorited.should be_false
417
+ first.user.id.should == 4243
418
+ first.user.name.should == 'John Nunemaker'
419
+ first.user.screen_name.should == 'jnunemaker'
420
+ first.user.location.should == 'Mishawaka, IN, US'
421
+ first.user.description.should == 'Loves his wife, ruby, notre dame football and iu basketball'
422
+ first.user.profile_image_url.should == 'http://s3.amazonaws.com/twitter_production/profile_images/53781608/Photo_75_normal.jpg'
423
+ first.user.url.should == 'http://addictedtonew.com'
424
+ first.user.protected.should be_false
425
+ first.user.followers_count.should == 486
426
+ end
427
+
428
+ it "should parse xml containing the desired element as root node" do
429
+ address = Address.parse(fixture_file('address.xml'), :single => true)
430
+ address.street.should == 'Milchstrasse'
431
+ address.postcode.should == '26131'
432
+ address.housenumber.should == '23'
433
+ address.city.should == 'Oldenburg'
434
+ address.country.should == 'Germany'
435
+ end
436
+
437
+ it "should parse xml with default namespace (amazon)" do
438
+ file_contents = fixture_file('pita.xml')
439
+ items = PITA::Items.parse(file_contents, :single => true)
440
+ items.total_results.should == 22
441
+ items.total_pages.should == 3
442
+ first = items.items[0]
443
+ second = items.items[1]
444
+ first.asin.should == '0321480791'
445
+ first.point.should == '38.5351715088 -121.7948684692'
446
+ first.detail_page_url.should be_a_kind_of(URI)
447
+ 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'
448
+ first.manufacturer.should == 'Addison-Wesley Professional'
449
+ first.product_group.should == '<ProductGroup>Book</ProductGroup>'
450
+ second.asin.should == '047022388X'
451
+ second.manufacturer.should == 'Wrox'
452
+ end
453
+
454
+ it "should parse xml that has attributes of elements" do
455
+ items = CurrentWeather.parse(fixture_file('current_weather.xml'))
456
+ first = items[0]
457
+ first.temperature.should == 51
458
+ first.feels_like.should == 51
459
+ first.current_condition.should == 'Sunny'
460
+ first.current_condition.icon.should == 'http://deskwx.weatherbug.com/images/Forecast/icons/cond007.gif'
461
+ end
462
+
463
+ it "should parse xml with nested elements" do
464
+ radars = Radar.parse(fixture_file('radar.xml'))
465
+ first = radars[0]
466
+ first.places.size.should == 1
467
+ first.places[0].name.should == 'Store'
468
+ second = radars[1]
469
+ second.places.size.should == 0
470
+ third = radars[2]
471
+ third.places.size.should == 2
472
+ third.places[0].name.should == 'Work'
473
+ third.places[1].name.should == 'Home'
474
+ end
475
+
476
+ it "should parse xml that has elements with dashes" do
477
+ commit = GitHub::Commit.parse(fixture_file('commit.xml'))
478
+ commit.message.should == "move commands.rb and helpers.rb into commands/ dir"
479
+ commit.url.should == "http://github.com/defunkt/github-gem/commit/c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
480
+ commit.id.should == "c26d4ce9807ecf57d3f9eefe19ae64e75bcaaa8b"
481
+ commit.committed_date.should == Date.parse("2008-03-02T16:45:41-08:00")
482
+ commit.tree.should == "28a1a1ca3e663d35ba8bf07d3f1781af71359b76"
483
+ end
484
+
485
+ it "should parse xml with no namespace" do
486
+ product = Product.parse(fixture_file('product_no_namespace.xml'), :single => true)
487
+ product.title.should == "A Title"
488
+ product.feature_bullets.bug.should == 'This is a bug'
489
+ product.feature_bullets.features.size.should == 2
490
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
491
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
492
+ end
493
+
494
+ it "should parse xml with default namespace" do
495
+ product = Product.parse(fixture_file('product_default_namespace.xml'), :single => true)
496
+ product.title.should == "A Title"
497
+ product.feature_bullets.bug.should == 'This is a bug'
498
+ product.feature_bullets.features.size.should == 2
499
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
500
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
501
+ end
502
+
503
+ it "should parse xml with single namespace" do
504
+ product = Product.parse(fixture_file('product_single_namespace.xml'), :single => true)
505
+ product.title.should == "A Title"
506
+ product.feature_bullets.bug.should == 'This is a bug'
507
+ product.feature_bullets.features.size.should == 2
508
+ product.feature_bullets.features[0].name.should == 'This is feature text 1'
509
+ product.feature_bullets.features[1].name.should == 'This is feature text 2'
510
+ end
511
+
512
+ it "should parse xml with multiple namespaces" do
513
+ track = FedEx::TrackReply.parse(fixture_file('multiple_namespaces.xml'))
514
+ track.highest_severity.should == 'SUCCESS'
515
+ track.more_data.should be_false
516
+ notification = track.notifications.first
517
+ notification.code.should == 0
518
+ notification.localized_message.should == 'Request was successfully processed.'
519
+ notification.message.should == 'Request was successfully processed.'
520
+ notification.severity.should == 'SUCCESS'
521
+ notification.source.should == 'trck'
522
+ detail = track.trackdetails.first
523
+ detail.carrier_code.should == 'FDXG'
524
+ detail.est_delivery.should == '2009-01-02T00:00:00'
525
+ detail.service_info.should == 'Ground-Package Returns Program-Domestic'
526
+ detail.status_code.should == 'OD'
527
+ detail.status_desc.should == 'On FedEx vehicle for delivery'
528
+ detail.tracking_number.should == '9611018034267800045212'
529
+ detail.weight.units.should == 'LB'
530
+ detail.weight.value.should == 2
531
+ events = detail.events
532
+ events.size.should == 10
533
+ first_event = events[0]
534
+ first_event.eventdescription.should == 'On FedEx vehicle for delivery'
535
+ first_event.eventtype.should == 'OD'
536
+ first_event.timestamp.should == '2009-01-02T06:00:00'
537
+ first_event.address.city.should == 'WICHITA'
538
+ first_event.address.countrycode.should == 'US'
539
+ first_event.address.residential.should be_false
540
+ first_event.address.state.should == 'KS'
541
+ first_event.address.zip.should == '67226'
542
+ last_event = events[-1]
543
+ last_event.eventdescription.should == 'In FedEx possession'
544
+ last_event.eventtype.should == 'IP'
545
+ last_event.timestamp.should == '2008-12-27T09:40:00'
546
+ last_event.address.city.should == 'LONGWOOD'
547
+ last_event.address.countrycode.should == 'US'
548
+ last_event.address.residential.should be_false
549
+ last_event.address.state.should == 'FL'
550
+ last_event.address.zip.should == '327506398'
551
+ track.tran_detail.cust_tran_id.should == '20090102-111321'
552
+ end
553
+
554
+ it "should not parse deep for elements unless deep option is set" do
555
+ tree = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
556
+ tree.persons.person.size.should_not == 2
557
+ tree.persons.person.size.should == 1
558
+ end
559
+
560
+ it "should parse family search xml" do
561
+ tree = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
562
+ tree.version.should == '1.0.20071213.942'
563
+ tree.status_message.should == 'OK'
564
+ tree.status_code.should == '200'
565
+ tree.persons.person.size.should == 1
566
+ tree.persons.person.first.version.should == '1199378491000'
567
+ tree.persons.person.first.modified.should == Time.utc(2008, 1, 3, 16, 41, 31) # 2008-01-03T09:41:31-07:00
568
+ tree.persons.person.first.id.should == 'KWQS-BBQ'
569
+ # namespaced elements (by url)
570
+ tree.persons.person.first.information.alternateIds.ids.should_not be_kind_of(String)
571
+ tree.persons.person.first.information.alternateIds.ids.size.should == 8
572
+ tree.persons.person.first.information.gender.should == 'Male'
573
+ tree.persons.person.first.information.living.should == false
574
+ end
575
+
576
+ it "should not set values for missing attributes" do
577
+ posts = Post.parse(fixture_file("partial_posts.xml"))
578
+ first = posts.first
579
+ first.href.should be_nil
580
+ first.hash.should be_nil
581
+ first.description.should be_nil
582
+ first.tag.should be_nil
583
+ first.time.should be_nil
584
+ first.others.should be_nil
585
+ first.extended.should be_nil
586
+ end
587
+
588
+ describe "serializing to xml" do
589
+
590
+ it "should return xml with elements representing the objects" do
591
+ address = Address.parse(fixture_file('address.xml'), :single => true)
592
+ xml = address.to_xml
593
+ xml.should be_kind_of(String)
594
+ # Would love to use libxml-ruby if xml_helper.rb were converted
595
+ doc = REXML::Document.new xml
596
+ doc.should have_nodes("/address", 1)
597
+ doc.should have_nodes("/address/street", 1)
598
+ doc.should match_xpath("/address/street","Milchstrasse")
599
+ doc.should match_xpath("/address/housenumber","23")
600
+ doc.should match_xpath("/address/postcode","26131")
601
+ doc.should match_xpath("/address/city","Oldenburg")
602
+ doc.should match_xpath("/address/country","Germany")
603
+ end
604
+
605
+ it "should include an xml version/encoding header" do
606
+ address = Address.new
607
+ xml = address.to_xml
608
+ xml.should =~ /<?xml version=".*" encoding=".*"?>/
609
+ end
610
+
611
+ it "should return xml with non-primitive elements from ruby objects" do
612
+ posts = Posts.parse(fixture_file('posts.xml'))
613
+ xml = posts.to_xml
614
+ doc = REXML::Document.new xml
615
+ doc.should have_nodes("/posts",1)
616
+ doc.should have_nodes("/posts/post",20)
617
+ end
618
+
619
+ it "should return xml with attributes representing 'attribute' objects" do
620
+ posts = Posts.parse(fixture_file('posts.xml'))
621
+ xml = posts.to_xml
622
+ doc = REXML::Document.new xml
623
+ doc.should have_nodes("/posts/post",20)
624
+ doc.should match_xpath("/posts/post[1]/@href","http://roxml.rubyforge.org/")
625
+ doc.should match_xpath("/posts/post[1]/@hash","19bba2ab667be03a19f67fb67dc56917")
626
+ doc.should match_xpath("/posts/post[1]/@description","ROXML - Ruby Object to XML Mapping Library")
627
+ doc.should match_xpath("/posts/post[1]/@tag","ruby xml gems mapping")
628
+ doc.should match_xpath("/posts/post[1]/@others","56")
629
+ doc.should match_xpath("/posts/post[1]/@extended","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.")
630
+ end
631
+
632
+ it "should be able to create new objects and serialize to xml" do
633
+ posts = Posts.new
634
+ posts.user = 'jimmyz'
635
+ posts.tag = 'happymapper'
636
+ posts.post << Post.new
637
+ posts.post << Post.new
638
+ xml = posts.to_xml
639
+ doc = REXML::Document.new xml
640
+ doc.should have_nodes("/posts",1)
641
+ doc.should have_nodes("/posts/post", 2)
642
+ doc.should match_xpath("/posts/@user",'jimmyz')
643
+ doc.should match_xpath("/posts/@tag",'happymapper')
644
+ end
645
+
646
+ it "should not serialize attributes or elements that are nil" do
647
+ posts = Posts.new
648
+ xml = posts.to_xml
649
+ doc = REXML::Document.new xml
650
+ doc.should have_nodes("/posts",1)
651
+ doc.should have_nodes("/posts/post",0)
652
+ doc.should have_nodes("/posts/@user",0)
653
+ end
654
+
655
+ it "should not serialize elements that are nil" do
656
+ ft = FamilySearch::FamilyTree.new
657
+ #ft has nil persons element should not raise error
658
+ lambda{
659
+ ft.to_xml
660
+ }.should_not raise_error
661
+
662
+ # Check for nil elements of primitive type
663
+ info = FamilySearch::Information.new
664
+ namespace = {'fsapi-v1' => 'http://api.familysearch.org/v1'}
665
+ doc = REXML::Document.new info.to_xml
666
+ doc.should have_nodes("/fsapi-v1:information/fsapi-v1:gender",0,namespace)
667
+ doc.should have_nodes("/fsapi-v1:information/fsapi-v1:living",0,namespace)
668
+ end
669
+
670
+ describe "with namespace url and prefix" do
671
+ before(:all) do
672
+ ft = FamilySearch::FamilyTree.parse(fixture_file('family_tree.xml'))
673
+ xml = ft.to_xml
674
+ @doc = REXML::Document.new xml
675
+ @namespaces = {
676
+ 'fsapi-v1' => 'http://api.familysearch.org/v1',
677
+ 'fs' => 'http://api.familysearch.org/familytree/v1'
678
+ }
679
+ end
680
+
681
+ it "should add namespaces to the root element that is being serialized" do
682
+ @doc.should match_xpath("/fs:familytree/@xmlns:fsapi-v1",'http://api.familysearch.org/v1',@namespaces)
683
+ end
684
+
685
+ it "should serialize elements with a namespace url and prefix" do
686
+ base_xpath = "/fs:familytree/fs:persons/fs:person[1]"
687
+ @doc.should have_nodes("#{base_xpath}/fsapi-v1:information",1,@namespaces)
688
+ @doc.should have_nodes("#{base_xpath}/fsapi-v1:information/fsapi-v1:alternateIds",1,@namespaces)
689
+ @doc.should have_nodes("#{base_xpath}/fsapi-v1:information/fsapi-v1:alternateIds/fsapi-v1:id",8,@namespaces)
690
+ end
691
+ end
692
+
693
+ end
694
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --colour