instructure-happymapper 0.5.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +35 -0
  3. data/README.md +605 -0
  4. data/lib/happymapper.rb +767 -0
  5. data/lib/happymapper/anonymous_mapper.rb +114 -0
  6. data/lib/happymapper/attribute.rb +21 -0
  7. data/lib/happymapper/element.rb +55 -0
  8. data/lib/happymapper/item.rb +160 -0
  9. data/lib/happymapper/supported_types.rb +140 -0
  10. data/lib/happymapper/text_node.rb +8 -0
  11. data/lib/happymapper/version.rb +3 -0
  12. data/spec/attribute_default_value_spec.rb +50 -0
  13. data/spec/attributes_spec.rb +36 -0
  14. data/spec/fixtures/address.xml +9 -0
  15. data/spec/fixtures/ambigous_items.xml +22 -0
  16. data/spec/fixtures/analytics.xml +61 -0
  17. data/spec/fixtures/analytics_profile.xml +127 -0
  18. data/spec/fixtures/atom.xml +19 -0
  19. data/spec/fixtures/commit.xml +52 -0
  20. data/spec/fixtures/current_weather.xml +89 -0
  21. data/spec/fixtures/current_weather_missing_elements.xml +18 -0
  22. data/spec/fixtures/default_namespace_combi.xml +6 -0
  23. data/spec/fixtures/dictionary.xml +20 -0
  24. data/spec/fixtures/family_tree.xml +21 -0
  25. data/spec/fixtures/inagy.xml +85 -0
  26. data/spec/fixtures/lastfm.xml +355 -0
  27. data/spec/fixtures/multiple_namespaces.xml +170 -0
  28. data/spec/fixtures/multiple_primitives.xml +5 -0
  29. data/spec/fixtures/optional_attributes.xml +6 -0
  30. data/spec/fixtures/pita.xml +133 -0
  31. data/spec/fixtures/posts.xml +23 -0
  32. data/spec/fixtures/product_default_namespace.xml +18 -0
  33. data/spec/fixtures/product_no_namespace.xml +10 -0
  34. data/spec/fixtures/product_single_namespace.xml +10 -0
  35. data/spec/fixtures/quarters.xml +19 -0
  36. data/spec/fixtures/radar.xml +21 -0
  37. data/spec/fixtures/set_config_options.xml +3 -0
  38. data/spec/fixtures/statuses.xml +422 -0
  39. data/spec/fixtures/subclass_namespace.xml +50 -0
  40. data/spec/fixtures/wrapper.xml +11 -0
  41. data/spec/happymapper/attribute_spec.rb +12 -0
  42. data/spec/happymapper/element_spec.rb +9 -0
  43. data/spec/happymapper/item_spec.rb +115 -0
  44. data/spec/happymapper/text_node_spec.rb +9 -0
  45. data/spec/happymapper_parse_spec.rb +113 -0
  46. data/spec/happymapper_spec.rb +1116 -0
  47. data/spec/has_many_empty_array_spec.rb +43 -0
  48. data/spec/ignay_spec.rb +95 -0
  49. data/spec/inheritance_spec.rb +107 -0
  50. data/spec/mixed_namespaces_spec.rb +61 -0
  51. data/spec/parse_with_object_to_update_spec.rb +111 -0
  52. data/spec/spec_helper.rb +7 -0
  53. data/spec/to_xml_spec.rb +200 -0
  54. data/spec/to_xml_with_namespaces_spec.rb +196 -0
  55. data/spec/wilcard_tag_name_spec.rb +96 -0
  56. data/spec/wrap_spec.rb +82 -0
  57. data/spec/xpath_spec.rb +89 -0
  58. metadata +183 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 01735d03f95fbadc43747b18bcd20296e0be6b47
4
+ data.tar.gz: 9941150d93d1f3e577d239db907bae3443e36158
5
+ SHA512:
6
+ metadata.gz: b0ef9e2d0c8451d4da2ff9cc8a1043e3c25c0dea2f8996494a51a580e14d6b5b2b8ecd1fb4bd45454a9c55f4036b35dc98b9c38406d6530c54f860565b3818cf
7
+ data.tar.gz: 5be25433ae8288cb48f8e31222d0b00343415830b7922247405e11455a74036e9352885f47de643584fdfd8f80ba47dccbc4efeef60168b8a6d23b7368a8617f
@@ -0,0 +1,35 @@
1
+ ## 0.5.9 / 2014-02-18
2
+
3
+ * Correctly output boolean element value 'false' (confusion)
4
+
5
+ ## 0.5.8 / 2013-10-12
6
+
7
+ * Allow child elements to remove their parent's namespacing (dcarneiro)
8
+ * has_many elements were returning nil because the tag name was being ignored (haarts)
9
+ * Subclassed happymapper classes are allowed to override elements (benoist)
10
+ * Attributes on elements with dashes will properly created methods (alex-klepa)
11
+ * 'Embedded' attributes break parsing when parent element is not present (geoffwa)
12
+
13
+ ## 0.5.7 / 2012-10-29
14
+
15
+ ## 0.5.6 / 2012-10-29
16
+
17
+ * Add possibility to give a configuration block to Nokogiri when parsing (DieboldInc).
18
+
19
+ ## 0.5.5 / 2012-09-30
20
+
21
+ * Fix for Boolean attributes to ensure that they parse correctly (zrob)
22
+
23
+ ## 0.5.4/ 2012-09-25
24
+
25
+ * the #wrap method allows you to better model xml content that is buried deep
26
+ within the xml. This implementation addresses issues with calling #to_xml
27
+ with content that was parsed from an xpath. (zrob)
28
+
29
+ * Parent HappyMapper classes may dictate the name of the tag for the child
30
+ HappyMapper instances. (zrob)
31
+
32
+ ## 0.5.3/ 2012-09-23
33
+
34
+ * String is the default type for parsed fields. (crv)
35
+ * Update the attributes of an existing HappyMapper instance with new XML (benoist)
@@ -0,0 +1,605 @@
1
+ HappyMapper
2
+ ===========
3
+
4
+ Happymapper allows you to parse XML data and convert it quickly and easily into ruby data structures.
5
+
6
+ This project is a fork of the great work done first by
7
+ [jnunemaker](https://github.com/jnunemaker/happymapper).
8
+
9
+ ## Major Differences
10
+
11
+ * [Nokogiri](http://nokogiri.org/) support
12
+ * Text nodes parsing
13
+ * Raw XML content parsing
14
+ * `#to_xml` support utilizing the same HappyMapper tags
15
+ * Numerous fixes for namespaces when using composition of classes
16
+ * Fixes for instances of XML where a namespace is defined but no elements with that namespace are found
17
+
18
+ ## Installation
19
+
20
+ ### [Rubygems](https://rubyygems.org/gems/nokogiri-happymapper)
21
+
22
+ $ gem install nokogiri-happymapper
23
+
24
+ ### [Bundler](http://gembundler.com/)
25
+ Add the `nokogiri-happymapper` gem to your project's `Gemfile`.
26
+
27
+ gem 'nokogiri-happymapper', :require => 'happymapper'
28
+
29
+ Run the bundler command to install the gem:
30
+
31
+ $ bundle install
32
+
33
+ # Examples
34
+
35
+ Let's start with a simple example to get our feet wet. Here we have a simple example of XML that defines some address information:
36
+
37
+ <address>
38
+ <street>Milchstrasse</street>
39
+ <housenumber>23</housenumber>
40
+ <postcode>26131</postcode>
41
+ <city>Oldenburg</city>
42
+ <country code="de">Germany</country>
43
+ </address>
44
+
45
+ Happymapper provides support for simple, zero configuration parsing as well as the ability to model the XML content in classes.
46
+
47
+ ## HappyMapper.parse(XML)
48
+
49
+ With no classes or configuration you can parse the example XML with little effort:
50
+
51
+ ```ruby
52
+ address = HappyMapper.parse(ADDRESS_XML_DATA)
53
+ address.street # => Milchstrasse
54
+ address.housenumber # => 23
55
+ address.postcode # => 26131
56
+ address.city # => Oldenburg
57
+ address.country.code # => de
58
+ address.country.content # => Germany
59
+ ```
60
+
61
+ It is important to be aware that this no configuration parsing is limited in capacity:
62
+
63
+ * All element names are converted to accessor methods with [underscorized](http://rubydoc.info/gems/activesupport/ActiveSupport/Inflector:underscore) names
64
+ * All value fields are left as String types
65
+ * Determining if there is just one or multiple child elements is hard, so it assumes it is one until it finds another with the same name.
66
+
67
+ ## Address.parse(XML)
68
+
69
+ Happymapper will let you easily model this information as a class:
70
+
71
+ ```ruby
72
+ require 'happymapper'
73
+
74
+ class Address
75
+ include HappyMapper
76
+
77
+ tag 'address'
78
+ element :street, String, :tag => 'street'
79
+ element :postcode, String, :tag => 'postcode'
80
+ element :housenumber, Integer, :tag => 'housenumber'
81
+ element :city, String, :tag => 'city'
82
+ element :country, String, :tag => 'country'
83
+ end
84
+ ```
85
+
86
+ To make a class HappyMapper compatible you simply `include HappyMapper` within the class definition. This takes care of all the work of defining all the speciality methods and magic you need to get running. As you can see we immediately start using these methods.
87
+
88
+ * `tag` matches the name of the XML tag name 'address'.
89
+
90
+ * `element` defines accessor methods for the specified symbol (e.g. `:street`,`:housenumber`) that will return the class type (e.g. `String`,`Integer`) of the XML tag specified (e.g. `:tag => 'street'`, `:tag => 'housenumber'`).
91
+
92
+ When you define an element with an accessor with the same name as the tag, this is the case for all the examples above, you can omit the `:tag`. These two element declaration are equivalent to each other:
93
+
94
+ ```ruby
95
+ element :street, String, :tag => 'street'
96
+ element :street, String
97
+ ```
98
+
99
+ Including the additional tag element is not going to hurt anything and in some cases will make it absolutely clear how these elements map to the XML. However, once you know this rule, it is hard not to want to save yourself the keystrokes.
100
+
101
+ Instead of `element` you may also use `has_one`:
102
+
103
+ ```ruby
104
+ element :street, String, :tag => 'street'
105
+ element :street, String
106
+ has_one :street, String
107
+ ```
108
+
109
+ These three statements are equivalent to each other.
110
+
111
+ ## Parsing
112
+
113
+ With the mapping of the address XML articulated in our Address class it is time to parse the data:
114
+
115
+ ```ruby
116
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
117
+ puts address.street
118
+ ```
119
+
120
+ Assuming that the constant `ADDRESS_XML_DATA` contains a string representation of the address XML data this is fairly straight-forward save for the `parse` method.
121
+
122
+ The `parse` method, like `tag` and `element` are all added when you included HappyMapper in the class. Parse is a wonderful, magical place that converts all these declarations that you have made into the data structure you are about to know and love.
123
+
124
+ But what about the `:single => true`? Right, that is because by default when your object is all done parsing it will be an array. In this case an array with one element, but an array none the less. So the following are equivalent to each other:
125
+
126
+ ```ruby
127
+ address = Address.parse(ADDRESS_XML_DATA).first
128
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
129
+ ```
130
+
131
+ The first one returns an array and we return the first instance, the second will do that work for us inside of parse.
132
+
133
+ ## Multiple Elements Mapping
134
+
135
+ What if our address XML was a little different, perhaps we allowed multiple streets:
136
+
137
+ <address>
138
+ <street>Milchstrasse</street>
139
+ <street>Another Street</street>
140
+ <housenumber>23</housenumber>
141
+ <postcode>26131</postcode>
142
+ <city>Oldenburg</city>
143
+ <country code="de">Germany</country>
144
+ </address>
145
+
146
+ Similar to `element` or `has_one`, the declaration for when you have multiple elements you simply use:
147
+
148
+ ```ruby
149
+ has_many :streets, String, :tag => 'street'
150
+ ```
151
+
152
+ Your resulting `streets` method will now return an array.
153
+
154
+ ```ruby
155
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
156
+ puts address.streets.join('\n')
157
+ ```
158
+
159
+ Imagine that you have to write `streets.join('\n')` for the rest of eternity throughout your code. It would be a nightmare and one that you could avoid by creating your own convenience method.
160
+
161
+ ```ruby
162
+ require 'happymapper'
163
+
164
+ class Address
165
+ include HappyMapper
166
+
167
+ tag 'address'
168
+
169
+ has_many :streets, String
170
+
171
+ def streets
172
+ @streets.join('\n')
173
+ end
174
+
175
+ element :postcode, String, :tag => 'postcode'
176
+ element :housenumber, String, :tag => 'housenumber'
177
+ element :city, String, :tag => 'city'
178
+ element :country, String, :tag => 'country'
179
+ end
180
+ ```
181
+
182
+ Now when we call the method `streets` we get a single value, but we still have the instance variable `@streets` if we ever need to the values as an array.
183
+
184
+
185
+ ## Attribute Mapping
186
+
187
+ <address location='home'>
188
+ <street>Milchstrasse</street>
189
+ <street>Another Street</street>
190
+ <housenumber>23</housenumber>
191
+ <postcode>26131</postcode>
192
+ <city>Oldenburg</city>
193
+ <country code="de">Germany</country>
194
+ </address>
195
+
196
+ Attributes are absolutely the same as `element` or `has_many`
197
+
198
+ ```ruby
199
+ attribute :location, String, :tag => 'location
200
+ ```
201
+
202
+ Again, you can omit the tag if the attribute accessor symbol matches the name of the attribute.
203
+
204
+
205
+ ### Attributes On Empty Child Elements
206
+
207
+ <feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
208
+ <id>tag:all-the-episodes.heroku.com,2005:/tv_shows</id>
209
+ <link rel="alternate" type="text/html" href="http://all-the-episodes.heroku.com"/>
210
+ <link rel="self" type="application/atom+xml" href="http://all-the-episodes.heroku.com/tv_shows.atom"/>
211
+ <title>TV Shows</title>
212
+ <updated>2011-07-10T06:52:27Z</updated>
213
+ </feed>
214
+
215
+ In this case you would need to map an element to a new `Link` class just to access `<link>`s attributes, except that there is an alternate syntax. Instead of
216
+
217
+ ```ruby
218
+ class Feed
219
+ # ....
220
+ has_many :links, Link, :tag => 'link', :xpath => '.'
221
+ end
222
+
223
+ class Link
224
+ include HappyMapper
225
+
226
+ attribute :rel, String
227
+ attribute :type, String
228
+ attribute :href, String
229
+ end
230
+ ```
231
+
232
+ You can drop the `Link` class and simply replace the `has_many` on `Feed` with
233
+
234
+ ```ruby
235
+ element :link, String, :single => false, :attributes => { :rel => String, :type => String, :href => String }
236
+ ```
237
+
238
+ As there is no content, the type given for `:link` (`String` above) is irrelevant, but `nil` won't work and other types may try to perform typecasting and fail. You can omit the :single => false for elements that only occur once within their parent.
239
+
240
+ This syntax is most appropriate for elements that (a) have attributes but no content and (b) only occur at only one level of the heirarchy. If `<feed>` contained another element that also contained a `<link>` (as atom feeds generally do) it would be DRY-er to use the first syntax, i.e. with a separate `Link` class.
241
+
242
+
243
+ ## Class composition (and Text Node)
244
+
245
+ Our address has a country and that country element has a code. Up until this point we neglected it as we declared a `country` as being a `String`.
246
+
247
+ <address location='home'>
248
+ <street>Milchstrasse</street>
249
+ <street>Another Street</street>
250
+ <housenumber>23</housenumber>
251
+ <postcode>26131</postcode>
252
+ <city>Oldenburg</city>
253
+ <country code="de">Germany</country>
254
+ </address>
255
+
256
+ Well if we only going to parse country, on it's own, we would likely create a class mapping for it.
257
+
258
+ ```ruby
259
+ class Country
260
+ include HappyMapper
261
+
262
+ tag 'country'
263
+
264
+ attribute :code, String
265
+ content :name, String
266
+ end
267
+ ```
268
+
269
+ We are utilizing an `attribute` declaration and a new declaration called `content`.
270
+
271
+ * `content` is used when you want the text contained within the element
272
+
273
+ Awesome, now if we were to redeclare our `Address` class we would use our new `Country` class.
274
+
275
+ ```ruby
276
+ class Address
277
+ include HappyMapper
278
+
279
+ tag 'address'
280
+
281
+ has_many :streets, String, :tag => 'street'
282
+
283
+ def streets
284
+ @streets.join('\n')
285
+ end
286
+
287
+ element :postcode, String, :tag => 'postcode'
288
+ element :housenumber, String, :tag => 'housenumber'
289
+ element :city, String, :tag => 'city'
290
+ element :country, Country, :tag => 'country'
291
+ end
292
+ ```
293
+
294
+ Instead of `String`, `Boolean`, or `Integer` we say that it is a `Country` and HappyMapper takes care of the details of continuing the XML mapping through the country element.
295
+
296
+ ```ruby
297
+ address = Address.parse(ADDRESS_XML_DATA, :single => true)
298
+ puts address.country.code
299
+ ```
300
+
301
+ A quick note, in the above example we used the constant `Country`. We could have used `'Country'`. The nice part of using the latter declaration, enclosed in quotes, is that you do not have to define your class before this class. So Country and Address can live in separate files and as long as both constants are available when it comes time to parse you are golden.
302
+
303
+ ## Custom XPATH
304
+
305
+ ### Has One, Has Many
306
+
307
+ Getting to elements deep down within your XML can be a little more work if you did not have xpath support. Consider the following example:
308
+
309
+ <media>
310
+ <gallery>
311
+ <title href="htttp://fishlovers.org/friends">Friends Who Like Fish</title>
312
+ <picture>
313
+ <name>Burtie Sanchez</name>
314
+ <img>burtie01.png</img>
315
+ </picture>
316
+ </gallery>
317
+ <picture>
318
+ <name>Unsorted Photo</name>
319
+ <img>bestfriends.png</img>
320
+ </picture>
321
+ </media>
322
+
323
+ You may want to map the sub-elements contained buried in the 'gallery' as top level items in the media. Traditionally you could use class composition to accomplish this task, however, using the xpath attribute you have the ability to shortcut some of that work.
324
+
325
+ ```ruby
326
+ class Media
327
+ include HappyMapper
328
+
329
+ has_one :title, String, :xpath => 'gallery/title'
330
+ has_one :link, String, :xpath => 'gallery/title/@href'
331
+ end
332
+ ```
333
+
334
+ ## Shared Functionality
335
+
336
+ ### Inheritance Approach
337
+
338
+ While mapping XML to objects you may arrive at a point where you have two or more very similar structures.
339
+
340
+ ```ruby
341
+ class Article
342
+ include HappyMapper
343
+
344
+ has_one :title, String
345
+ has_one :author, String
346
+ has_one :published, Time
347
+
348
+ has_one :entry, String
349
+
350
+ end
351
+
352
+ class Gallery
353
+ include HappyMapper
354
+
355
+ has_one :title, String
356
+ has_one :author, String
357
+ has_one :published, Time
358
+
359
+ has_many :photos, String
360
+
361
+ end
362
+ ```
363
+
364
+ In this example there are definitely two similarities between our two pieces of content. So much so that you might be included to create an inheritance structure to save yourself some keystrokes.
365
+
366
+ ```ruby
367
+ class Content
368
+ include HappyMapper
369
+
370
+ has_one :title, String
371
+ has_one :author, String
372
+ has_one :published, Time
373
+ end
374
+
375
+ class Article < Content
376
+ include HappyMapper
377
+
378
+ has_one :entry, String
379
+ end
380
+
381
+ class Gallery < Content
382
+ include HappyMapper
383
+
384
+ has_many :photos, String
385
+ end
386
+ ```
387
+
388
+ ### Module Mixins Approach
389
+
390
+ You can also solve the above problem through mixins.
391
+
392
+ ```ruby
393
+ module Content
394
+ def self.included(content)
395
+ content.has_one :title, String
396
+ content.has_one :author, String
397
+ content.has_one :published, Time
398
+ end
399
+
400
+ def published_time
401
+ @published.strftime("%H:%M:%S")
402
+ end
403
+ end
404
+
405
+ class Article
406
+ include HappyMapper
407
+
408
+ include Content
409
+ has_one :entry, String
410
+ end
411
+
412
+ class Gallery
413
+ include HappyMapper
414
+
415
+ include Content
416
+ has_many :photos, String
417
+ end
418
+ ```
419
+
420
+ Here, when we include `Content` in both of these classes the module method `#included` is called and our class is given as a parameter. So we take that opportunity to do some surgery and define our happymapper elements as well as any other methods that may rely on those instance variables that come along in the package.
421
+
422
+
423
+ ## Filtering with XPATH (non-greedy)
424
+
425
+ I ran into a case where I wanted to capture all the pictures that were directly under media, but not the ones contained within a gallery.
426
+
427
+ <media>
428
+ <gallery>
429
+ <picture>
430
+ <name>Burtie Sanchez</name>
431
+ <img>burtie01.png</img>
432
+ </picture>
433
+ </gallery>
434
+ <picture>
435
+ <name>Unsorted Photo</name>
436
+ <img>bestfriends.png</img>
437
+ </picture>
438
+ </media>
439
+
440
+ The following `Media` class is where I started:
441
+
442
+ ```ruby
443
+ require 'happymapper'
444
+
445
+ class Media
446
+ include HappyMapper
447
+
448
+ has_many :galleries, Gallery, :tag => 'gallery'
449
+ has_many :pictures, Picture, :tag => 'picture'
450
+ end
451
+ ```
452
+
453
+ However when I parsed the media xml the number of pictures returned to me was 2, not 1.
454
+
455
+ ```ruby
456
+ pictures = Media.parse(MEDIA_XML,:single => true).pictures
457
+ pictures.length.should == 1 # => Failed Expectation
458
+ ```
459
+
460
+ The reason that 2 elements are returned and not 1 is because the default
461
+ mappings are assigned XPATH './/' which makes them greedy. Essentially by
462
+ default it will find all elements with the tag 'pictures' at the current
463
+ level of the document and anywhere else within the document.
464
+
465
+ To limit an element from being greedy and only finding elements at the
466
+ level of the current node you can specify an XPATH.
467
+
468
+ ```ruby
469
+ has_many :pictures, Picture, :tag => 'picture', :xpath => '.'
470
+ ```
471
+
472
+ `.` states that we are only interested in pictures that can be found directly
473
+ under the current node. So when we parse again we will have only our one element.
474
+
475
+
476
+ ## Namespaces
477
+
478
+ Obviously your XML and these trivial examples are easy to map and parse because they lack the treacherous namespaces that befall most XML files.
479
+
480
+ Perhaps our `address` XML is really swarming with namespaces:
481
+
482
+ <prefix:address location='home' xmlns:prefix="http://www.unicornland.com/prefix">
483
+ <prefix:street>Milchstrasse</prefix:street>
484
+ <prefix:street>Another Street</prefix:street>
485
+ <prefix:housenumber>23</prefix:housenumber>
486
+ <prefix:postcode>26131</prefix:postcode>
487
+ <prefix:city>Oldenburg</prefix:city>
488
+ <prefix:country code="de">Germany</prefix:country>
489
+ </prefix:address>
490
+
491
+ Here again is our address example with a made up namespace called `prefix` that comes direct to use from unicornland, a very magical place indeed. Well we are going to have to do some work on our class definition and that simply adding this one liner to the `Address` class:
492
+
493
+ ```ruby
494
+ class Address
495
+ include HappyMapper
496
+
497
+ tag 'address'
498
+ namespace 'prefix'
499
+ # ... rest of the code ...
500
+ end
501
+ ```
502
+
503
+ Of course, if that is too easy for you, you can append a `:namespace => 'prefix` to every one of the elements that you defined.
504
+
505
+ ```ruby
506
+ has_many :street, String, :tag => 'street', :namespace => 'prefix'
507
+ element :postcode, String, :tag => 'postcode', :namespace => 'prefix'
508
+ element :housenumber, String, :tag => 'housenumber', :namespace => 'prefix'
509
+ element :city, String, :tag => 'city', :namespace => 'prefix'
510
+ element :country, Country, :tag => 'country', :namespace => 'prefix'
511
+ ```
512
+
513
+ I definitely recommend the former, as it saves you a whole hell of lot of typing. However, there are times when appending a namespace to an element declaration is important and that is when it has a different namespace then `namespsace 'prefix'`.
514
+
515
+ Imagine that our `country` actually belonged to a completely different namespace.
516
+
517
+ <prefix:address location='home' xmlns:prefix="http://www.unicornland.com/prefix"
518
+ xmlns:different="http://www.trollcountry.com/different">
519
+ <prefix:street>Milchstrasse</prefix:street>
520
+ <prefix:street>Another Street</prefix:street>
521
+ <prefix:housenumber>23</prefix:housenumber>
522
+ <prefix:postcode>26131</prefix:postcode>
523
+ <prefix:city>Oldenburg</prefix:city>
524
+ <different:country code="de">Germany</different:country>
525
+ </prefix:address>
526
+
527
+ Well we would need to specify that namespace:
528
+
529
+ ```ruby
530
+ element :country, Country, :tag => 'country', :namespace => 'different'
531
+ ```
532
+
533
+ With that we should be able to parse as we once did.
534
+
535
+ ## Large Datasets (in_groups_of)
536
+
537
+ When dealing with large sets of XML that simply cannot or should not be placed into memory the objects can be handled in groups through the `:in_groups_of` parameter.
538
+
539
+ ```ruby
540
+ Address.parse(LARGE_ADDRESS_XML_DATA,:in_groups_of => 5) do |group|
541
+ puts address.streets
542
+ end
543
+ ```
544
+
545
+ This trivial block will parse the large set of XML data and in groups of 5 addresses at a time display the streets.
546
+
547
+ ## Saving to XML
548
+
549
+ Saving a class to XML is as easy as calling `#to_xml`. The end result will be the current state of your object represented as xml. Let's cover some details that are sometimes necessary and features present to make your life easier.
550
+
551
+
552
+ ### :on_save
553
+
554
+ When you are saving data to xml it is often important to change or manipulate data to a particular format. For example, a time object:
555
+
556
+ ```ruby
557
+ has_one :published_time, Time, :on_save => lambda {|time| time.strftime("%H:%M:%S") if time }
558
+ ```
559
+
560
+ Here we add the options `:on_save` and specify a lambda which will be executed on the method call to `:published_time`.
561
+
562
+ ### :state_when_nil
563
+
564
+ When an element contains a nil value, or perhaps the result of the :on_save lambda correctly results in a nil value you will be happy that the element will not appear in the resulting XML. However, there are time when you will want to see that element and that's when `:state_when_nil` is there for you.
565
+
566
+ ```ruby
567
+ has_one :favorite_color, String, :state_when_nil => true
568
+ ```
569
+
570
+ The resulting XML will include the 'favorite_color' element even if the favorite color has not been specified.
571
+
572
+ ### :read_only
573
+
574
+ When an element, attribute, or text node is a value that you have no interest in
575
+ saving to XML, you can ensure that takes place by stating that it is `read only`.
576
+
577
+ ```ruby
578
+ has_one :modified, Boolean, :read_only => true
579
+ attribute :temporary, Boolean, :read_only => true
580
+ ```
581
+
582
+ This is useful if perhaps the incoming XML is different than the out-going XML.
583
+
584
+ ### namespaces
585
+
586
+ Parsing the XML to objects only required you to simply specify the prefix of the namespace you wanted to parse, when you persist to xml you will need to define your namespaces so that they are correctly captured.
587
+
588
+ ```ruby
589
+ class Address
590
+ include HappyMapper
591
+
592
+ register_namespace 'prefix', 'http://www.unicornland.com/prefix'
593
+ register_namespace 'different', 'http://www.trollcountry.com/different'
594
+
595
+ tag 'address'
596
+ namespace 'prefix'
597
+
598
+ has_many :street, String
599
+ element :postcode, String
600
+ element :housenumber, String
601
+ element :city, String
602
+ element :country, Country, :tag => 'country', :namespace => 'different'
603
+
604
+ end
605
+ ```