roar 0.12.1 → 0.12.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.markdown +4 -0
- data/README.markdown +559 -0
- data/examples/example.rb +368 -0
- data/examples/example_server.rb +18 -0
- data/lib/roar/representer/xml.rb +1 -1
- data/lib/roar/version.rb +1 -1
- data/test/hypermedia_feature_test.rb +57 -16
- data/test/test_helper.rb +4 -0
- metadata +5 -3
- data/README.textile +0 -331
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04161ea9b3e943c2369825c6a3c426cfae97550d
|
4
|
+
data.tar.gz: f7a7a96c45646273d7355938becde08c0549d8b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 766eddbf40e6931caaf003a895598799c1d40efea0ba4854a1938995c0a7c0a41b96cfe1e69593c9a89538b9426305aff2f34dcd617a60a95f14840398296e54
|
7
|
+
data.tar.gz: ec8585e4bc27fb429895db8d2026c70aade0b360d5b6d741d4d67c7eddc0306194947514061f8f1fe6361822ddceb5e1370b6ff671430c9628b5af5781c791e4
|
data/CHANGES.markdown
CHANGED
data/README.markdown
ADDED
@@ -0,0 +1,559 @@
|
|
1
|
+
# ROAR
|
2
|
+
|
3
|
+
_Resource-Oriented Architectures in Ruby._
|
4
|
+
|
5
|
+
## Introduction
|
6
|
+
|
7
|
+
Roar is a framework for parsing and rendering REST documents. Nothing more.
|
8
|
+
|
9
|
+
Representers let you define your API document structure and semantics. They allow both rendering representations from your models _and_ parsing documents to update your Ruby objects. The bi-directional nature of representers make them interesting for both server and client usage.
|
10
|
+
|
11
|
+
Roar comes with built-in JSON, JSON-HAL and XML support. Its highly modulare architecture provides features like coercion, hypermedia, HTTP transport, client caching and more.
|
12
|
+
|
13
|
+
Roar is completely framework-agnostic and loves being used in web kits like Rails, Webmachine, Sinatra, Padrino, etc. If you use Rails, consider [roar-rails](https://github.com/apotonick/roar-rails) for an enjoyable integration.
|
14
|
+
|
15
|
+
## Representable
|
16
|
+
|
17
|
+
Roar is just a thin layer on top of the [representable](https://github.com/apotonick/representable) gem. While Roar gives you a DSL and behaviour for creating hypermedia APIs, representable implements all the mapping functionality.
|
18
|
+
|
19
|
+
If in need for a feature, make sure to check the [representable API docs](https://github.com/apotonick/representable) first.
|
20
|
+
|
21
|
+
|
22
|
+
## Defining Representers
|
23
|
+
|
24
|
+
Let's see how representers work. They're fun to use.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'roar/representer/json'
|
28
|
+
|
29
|
+
module SongRepresenter
|
30
|
+
include Roar::Representer::JSON
|
31
|
+
|
32
|
+
property :title
|
33
|
+
end
|
34
|
+
```
|
35
|
+
|
36
|
+
API documents are defined using a representer module or decorator class. You can define plain attributes using the `::property` method.
|
37
|
+
|
38
|
+
Now let's assume we'd have `Song` which is an `ActiveRecord` class. Please note that Roar is not limited to ActiveRecord. In fact, it doesn't really care whether it's representing ActiveRecord, Datamapper or just an OpenStruct instance.
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
class Song < ActiveRecord
|
42
|
+
end
|
43
|
+
```
|
44
|
+
|
45
|
+
## Rendering
|
46
|
+
|
47
|
+
To render a document, you apply the representer to your model.
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
song = Song.new(title: "Fate")
|
51
|
+
song.extend(SongRepresenter)
|
52
|
+
|
53
|
+
song.to_json #=> {"title":"Fate"}
|
54
|
+
```
|
55
|
+
|
56
|
+
Here, the representer is injected into the actual model and gives us a new `#to_json` method.
|
57
|
+
|
58
|
+
## Parsing
|
59
|
+
|
60
|
+
The cool thing about representers is: they can be used for rendering and parsing. See how easy updating your model from a document is.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
song = Song.new
|
64
|
+
song.extend(SongRepresenter)
|
65
|
+
|
66
|
+
song.from_json('{"title":"Linoleum"}')
|
67
|
+
|
68
|
+
song.title #=> Linoleum
|
69
|
+
```
|
70
|
+
|
71
|
+
Again, `#from_json` comes from the representer and just updates the known properties.
|
72
|
+
|
73
|
+
Unknown attributes in the parsed document are simply ignored, making half-baked solutions like `strong_parameters` redundant.
|
74
|
+
|
75
|
+
|
76
|
+
## Decorator
|
77
|
+
|
78
|
+
Many people dislike `#extend` due to eventual performance issue or object pollution. If you're one of those, just go with a decorator representer. They almost work identical to the module approach we just discovered.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
require 'roar/decorator'
|
82
|
+
|
83
|
+
class SongRepresenter < Roar::Decorator
|
84
|
+
include Roar::Representer::JSON
|
85
|
+
|
86
|
+
property :title
|
87
|
+
end
|
88
|
+
```
|
89
|
+
In place of a module you use a class, the DSL inside is the same you already know.
|
90
|
+
|
91
|
+
```ruby
|
92
|
+
song = Song.new(title: "Medicine Balls")
|
93
|
+
|
94
|
+
SongRepresenter.new(song).to_json #=> {"title":"Medicine Balls"}
|
95
|
+
```
|
96
|
+
|
97
|
+
Here, the `song` objects gets wrapped (or "decorated") by the decorator. It is treated as immutuable - Roar won't mix in any behaviour.
|
98
|
+
|
99
|
+
Note that decorators and representer modules have identical features. You can parse, render, nest, go nuts with both of them.
|
100
|
+
|
101
|
+
However, in this README we'll use modules to illustrate this framework.
|
102
|
+
|
103
|
+
|
104
|
+
## Collections
|
105
|
+
|
106
|
+
Roar (or rather representable) also allows to map collections in documents.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
module SongRepresenter
|
110
|
+
include Roar::Representer::JSON
|
111
|
+
|
112
|
+
property :title
|
113
|
+
collection :composers
|
114
|
+
end
|
115
|
+
```
|
116
|
+
|
117
|
+
Where `::property` knows how to handle plain attributes, `::collection` does lists.
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
song = Song.new(title: "Roxanne", composers: ["Sting", "Stu Copeland"])
|
121
|
+
song.extend(SongRepresenter)
|
122
|
+
|
123
|
+
song.to_json #=> {"title":"Roxanne","composers":["Sting","Stu Copeland"]}
|
124
|
+
```
|
125
|
+
|
126
|
+
And, yes, this also works for parsing: `from_json` will create and populate the array of the `composers` attribute.
|
127
|
+
|
128
|
+
|
129
|
+
## Nesting
|
130
|
+
|
131
|
+
Now what if we need to tackle with collections of `Song`s? We need to implement an `Album` class.
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
class Album < ActiveRecord
|
135
|
+
has_many :songs
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
Another representer to represent.
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
module AlbumRepresenter
|
143
|
+
include Roar::Representer::JSON
|
144
|
+
|
145
|
+
property :title
|
146
|
+
collection :songs, extend: SongRepresenter, class: Song
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
Both `::property` and `::collection` accept options for nesting representers into representers.
|
151
|
+
|
152
|
+
The `extend:` option tells Roar which representer to use for the nested objects (here, the array items of the `album.songs` field). When parsing a document `class:` defines the nested object type.
|
153
|
+
|
154
|
+
Consider the following object setup.
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
album = Album.new(title: "True North")
|
158
|
+
album.songs << Song.new(title: "The Island")
|
159
|
+
album.songs << Song.new(:title => "Changing Tide")
|
160
|
+
```
|
161
|
+
|
162
|
+
You apply the `AlbumRepresenter` and you get a nested document.
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
album.extend(AlbumRepresenter)
|
166
|
+
|
167
|
+
album.to_json #=> {"title":"True North","songs":[{"title":"The Island"},{"title":"Changing Tide"}]}
|
168
|
+
```
|
169
|
+
|
170
|
+
This works vice-versa.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
album = Album.new
|
174
|
+
album.extend(AlbumRepresenter)
|
175
|
+
|
176
|
+
album.from_json('{"title":"Indestructible","songs":[{"title":"Tropical London"},{"title":"Roadblock"}]}')
|
177
|
+
|
178
|
+
puts album.songs[1] #=> #<Song title="Roadblock">
|
179
|
+
```
|
180
|
+
|
181
|
+
The nesting of two representers can map composed object as you find them in many many APIs.
|
182
|
+
|
183
|
+
|
184
|
+
## Inline Representer
|
185
|
+
|
186
|
+
Sometimes you don't wanna create two separate representers - although it makes them reusable across your app. Use inline representers if you're not intending this.
|
187
|
+
|
188
|
+
```ruby
|
189
|
+
module AlbumRepresenter
|
190
|
+
include Roar::Representer::JSON
|
191
|
+
|
192
|
+
property :title
|
193
|
+
|
194
|
+
collection :songs, class: Song do
|
195
|
+
property :title
|
196
|
+
end
|
197
|
+
end
|
198
|
+
```
|
199
|
+
|
200
|
+
This will give you the same rendering and parsing behaviour as in the previous example with just one module.
|
201
|
+
|
202
|
+
|
203
|
+
## Syncing Objects
|
204
|
+
|
205
|
+
Usually, when parsing, nested objects are created from scratch. If you want nested objects to be updated instead of being newly created, use `parse_strategy:`.
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
module AlbumRepresenter
|
209
|
+
include Roar::Representer::JSON
|
210
|
+
|
211
|
+
property :title
|
212
|
+
|
213
|
+
collection :songs, extend: SongRepresenter, parse_strategy: :sync
|
214
|
+
end
|
215
|
+
```
|
216
|
+
|
217
|
+
This will advise Roar to update existing `songs`.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
album.songs[0].object_id #=> 81431220
|
221
|
+
|
222
|
+
album.from_json('{"title":"True North","songs":[{"title":"Secret Society"},{"title":"Changing Tide"}]}')
|
223
|
+
|
224
|
+
album.songs[0].title #=> Secret Society
|
225
|
+
album.songs[0].object_id #=> 81431220
|
226
|
+
```
|
227
|
+
Roar didn't create a new `Song` instance but updated its attributes, only.
|
228
|
+
|
229
|
+
We're currently [working on](https://github.com/apotonick/roar/issues/85) better strategies to easily implement `POST` and `PUT` semantics in your APIs without having to worry about the nitty-gritties.
|
230
|
+
|
231
|
+
|
232
|
+
## Coercion
|
233
|
+
|
234
|
+
Roar provides coercion with the [virtus](https://github.com/solnic/virtus) gem.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
require 'roar/representer/feature/coercion'
|
238
|
+
|
239
|
+
module SongRepresenter
|
240
|
+
include Roar::Representer::JSON
|
241
|
+
include Roar::Representer::Feature::Coercion
|
242
|
+
|
243
|
+
property :title
|
244
|
+
property :released_at, type: DateTime
|
245
|
+
end
|
246
|
+
```
|
247
|
+
|
248
|
+
The `:type` option allows to set a virtus-compatible type.
|
249
|
+
|
250
|
+
```ruby
|
251
|
+
song = Song.new
|
252
|
+
song.extend(SongRepresenter)
|
253
|
+
|
254
|
+
song.from_json('{"released_at":"1981/03/31"}')
|
255
|
+
|
256
|
+
song.released_at #=> 1981-03-31T00:00:00+00:00
|
257
|
+
```
|
258
|
+
|
259
|
+
|
260
|
+
## More Features
|
261
|
+
|
262
|
+
Roar/representable gives you many more mapping features like [renaming attributes](https://github.com/apotonick/representable/#aliasing), [wrapping](https://github.com/apotonick/representable/#wrapping), [passing options](https://github.com/apotonick/representable/#passing-options), etc.
|
263
|
+
|
264
|
+
|
265
|
+
## Hypermedia
|
266
|
+
|
267
|
+
Roar comes with built-in support for embedding and processing hypermedia in your documents.
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
module SongRepresenter
|
271
|
+
include Roar::Representer::JSON
|
272
|
+
include Roar::Representer::Feature::Hypermedia
|
273
|
+
|
274
|
+
property :title
|
275
|
+
|
276
|
+
link :self do
|
277
|
+
"http://songs/#{title}"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
```
|
281
|
+
|
282
|
+
The `Hypermedia` feature allows declaring links using the `::link` method.
|
283
|
+
|
284
|
+
```ruby
|
285
|
+
song.extend(SongRepresenter)
|
286
|
+
song.to_json #=> {"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}
|
287
|
+
```
|
288
|
+
|
289
|
+
Per default, links are pushed into the hash using the `links` key. Link blocks are executed in represented context, allowing you to call any instance method of your model (here, we call `#title`).
|
290
|
+
|
291
|
+
Also, note that [roar-rails](https://github.com/apotonick/roar-rails) allows using URL helpers in link blocks.
|
292
|
+
|
293
|
+
|
294
|
+
## Passing Options
|
295
|
+
|
296
|
+
Sometimes you need more data in the link block. Data that's not available from the represented model.
|
297
|
+
|
298
|
+
```ruby
|
299
|
+
module SongRepresenter
|
300
|
+
include Roar::Representer::JSON
|
301
|
+
|
302
|
+
property :title
|
303
|
+
|
304
|
+
link :self do |opts|
|
305
|
+
"http://#{opts[:base_url]}songs/#{title}"
|
306
|
+
end
|
307
|
+
end
|
308
|
+
```
|
309
|
+
|
310
|
+
Pass this data to the rendering method.
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
song.to_json(base_url: "localhost:3001/")
|
314
|
+
```
|
315
|
+
|
316
|
+
Any options passed to `#to_json` will be available as block arguments in the link blocks.
|
317
|
+
|
318
|
+
|
319
|
+
## Consuming Hypermedia
|
320
|
+
|
321
|
+
Since we defined hypermedia attributes in the representer we can also consume this hypermedia when we parse documents.
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
song.from_json('{"title":"Roxanne","links":[{"rel":"self","href":"http://songs/Roxanne"}]}')
|
325
|
+
|
326
|
+
song.links[:self].href #=> "http://songs/Roxanne"
|
327
|
+
```
|
328
|
+
|
329
|
+
Reading link attributes works by using `#links[]` on the consuming instance.
|
330
|
+
|
331
|
+
This allows an easy way to discover hypermedia and build navigational logic on top.
|
332
|
+
|
333
|
+
|
334
|
+
## Media Formats
|
335
|
+
|
336
|
+
While Roar comes with a built-in hypermedia format, there's official media types that are widely recognized. Roar currently supports HAL and Collection+JSON. Support for Siren and JSON-API is planned when there's sponsors.
|
337
|
+
|
338
|
+
Simply by including a module you make your representer understand the media type. This makes it easy to change formats during evaluation.
|
339
|
+
|
340
|
+
## HAL-JSON
|
341
|
+
|
342
|
+
The [HAL](http://stateless.co/hal_specification.html) format is a simple media type that defines embedded resources and hypermedia.
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
require 'roar/representer/json/hal'
|
346
|
+
|
347
|
+
module SongRepresenter
|
348
|
+
include Roar::Representer::JSON::HAL
|
349
|
+
|
350
|
+
property :title
|
351
|
+
|
352
|
+
link :self do
|
353
|
+
"http://songs/#{title}"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
```
|
357
|
+
|
358
|
+
### Hypermedia
|
359
|
+
|
360
|
+
Including the `Roar::Representer::JSON::HAL` module adds some more DSL methods to your module. It still allows using `::link` but treats them slightly different.
|
361
|
+
|
362
|
+
```ruby
|
363
|
+
song.to_json
|
364
|
+
#=> {"title":"Roxanne","_links":{"self":{"href":"http://songs/Roxanne"}}}
|
365
|
+
```
|
366
|
+
|
367
|
+
According to the HAL specification, links are now key with their `rel` attribute under the `_links` key.
|
368
|
+
|
369
|
+
Parsing works like-wise: Roar will use the same setters as before but it knows how to read HAL.
|
370
|
+
|
371
|
+
### Nesting
|
372
|
+
|
373
|
+
Nested, or embedded, resources can be defined using the `:embedded` option.
|
374
|
+
|
375
|
+
```ruby
|
376
|
+
module AlbumRepresenter
|
377
|
+
include Roar::Representer::JSON::HAL
|
378
|
+
|
379
|
+
property :title
|
380
|
+
|
381
|
+
collection :songs, class: Song, embedded: true do
|
382
|
+
property :title
|
383
|
+
end
|
384
|
+
end
|
385
|
+
```
|
386
|
+
|
387
|
+
To embed a resource, you can use an inline representer or use `:extend` to specify the representer name.
|
388
|
+
|
389
|
+
```ruby
|
390
|
+
album.to_json
|
391
|
+
|
392
|
+
#=> {"title":"True North","_embedded":{"songs":[{"title":"The Island"},{"title":"Changing Tide"}]}}
|
393
|
+
```
|
394
|
+
|
395
|
+
HAL keys nested resources under the `_embedded` key and then by their type.
|
396
|
+
|
397
|
+
All HAL features in Roar are discussed in the [API docs](http://rdoc.info/github/apotonick/roar/Roar/Representer/JSON/HAL), including [array links](https://github.com/apotonick/roar/blob/master/lib/roar/representer/json/hal.rb#L176).
|
398
|
+
|
399
|
+
|
400
|
+
## Collection+JSON
|
401
|
+
|
402
|
+
The [Collection+JSON media format](http://amundsen.com/media-types/collection/) defines document format and semantics for requests. It is currently experimental as we're still exploring how we optimize the support with Roar. Let us know if you're using it.
|
403
|
+
|
404
|
+
```ruby
|
405
|
+
module SongRepresenter
|
406
|
+
include Roar::Representer::JSON::CollectionJSON
|
407
|
+
version "1.0"
|
408
|
+
href { "http://localhost/songs/" }
|
409
|
+
|
410
|
+
property :title
|
411
|
+
|
412
|
+
items(:class => Song) do
|
413
|
+
href { "//songs/#{title}" }
|
414
|
+
|
415
|
+
property :title, :prompt => "Song title"
|
416
|
+
|
417
|
+
link(:download) { "//songs/#{title}.mp3" }
|
418
|
+
end
|
419
|
+
|
420
|
+
template do
|
421
|
+
property :title, :prompt => "Song title"
|
422
|
+
end
|
423
|
+
|
424
|
+
queries do
|
425
|
+
link :search do
|
426
|
+
{:href => "//search", :data => [{:name => "q", :value => ""}]}
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
```
|
431
|
+
|
432
|
+
It renders a document following the Collection+JSON specs.
|
433
|
+
|
434
|
+
```
|
435
|
+
#=> {"collection":{
|
436
|
+
"template":{"data":[{"name":"title","value":null}]},
|
437
|
+
"queries":[{"rel":"search","href":"//search","data":[{"name":"q","value":""}]}],
|
438
|
+
"version":"1.0",
|
439
|
+
"href":"http://localhost/songs/",
|
440
|
+
"title":"Roxanne",
|
441
|
+
"items":null}}
|
442
|
+
```
|
443
|
+
|
444
|
+
We have big plans with this media format, as the object model in Roar plays nicely with Collection+JSON's API semantics.
|
445
|
+
|
446
|
+
|
447
|
+
## Client-Side Support
|
448
|
+
|
449
|
+
Being a bi-directional mapper that does rendering _and_ parsing, Roar representers are perfectly suitable for use in clients, too. In many projects, representers are shared as gems between server and client.
|
450
|
+
|
451
|
+
Consider the following shared representer.
|
452
|
+
|
453
|
+
```ruby
|
454
|
+
module SongRepresenter
|
455
|
+
include Roar::Representer::JSON
|
456
|
+
include Roar::Representer::Feature::Hypermedia
|
457
|
+
|
458
|
+
property :title
|
459
|
+
property :id
|
460
|
+
|
461
|
+
link :self do
|
462
|
+
"http://songs/#{title}"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
```
|
466
|
+
|
467
|
+
In a client where you don't have access to the database it is common to use `OpenStruct` classes as domain objects.
|
468
|
+
|
469
|
+
```ruby
|
470
|
+
require 'roar/representer/feature/client'
|
471
|
+
|
472
|
+
class Song < OpenStruct
|
473
|
+
include Roar::Representer::JSON
|
474
|
+
include SongRepresenter
|
475
|
+
include Roar::Representer::Feature::Client
|
476
|
+
end
|
477
|
+
```
|
478
|
+
|
479
|
+
### HTTP Support
|
480
|
+
|
481
|
+
The `Feature::Client` module mixes all necessary methods into the client class, e.g. it provides HTTP support
|
482
|
+
|
483
|
+
```ruby
|
484
|
+
song = Song.new(title: "Roxanne")
|
485
|
+
song.post("http://localhost:4567/songs", "application/json")
|
486
|
+
|
487
|
+
song.id #=> 42
|
488
|
+
```
|
489
|
+
|
490
|
+
What happens here?
|
491
|
+
|
492
|
+
* You're responsible for initializing the client object with attributes. This can happen with in the constructor or using accessors.
|
493
|
+
* `post` will use the included `SongRepresenter` to compile the document using `#to_json`.
|
494
|
+
* The document gets `POST`ed to the passed URL.
|
495
|
+
* If a document is returned, it is deserialized and the client's attributes are updated.
|
496
|
+
|
497
|
+
This is a very simple but efficient mechanism for working with representations in a client application.
|
498
|
+
|
499
|
+
Roar works with all HTTP request types, check out `GET`.
|
500
|
+
|
501
|
+
```ruby
|
502
|
+
song = Client::Song.new
|
503
|
+
song.get("http://localhost:4567/songs/1", "application/json")
|
504
|
+
|
505
|
+
song.title #=> "Roxanne"
|
506
|
+
song.links[:self].href #=> http://localhost/songs/1
|
507
|
+
```
|
508
|
+
|
509
|
+
As `GET` is not supposed to send any data, you can use `#get` on an empty object to populate it with the server data.
|
510
|
+
|
511
|
+
|
512
|
+
## XML
|
513
|
+
|
514
|
+
Roar also comes with XML support.
|
515
|
+
|
516
|
+
```ruby
|
517
|
+
module SongRepresenter
|
518
|
+
include Roar::Representer::XML
|
519
|
+
include Roar::Representer::Feature::Hypermedia
|
520
|
+
|
521
|
+
property :title
|
522
|
+
property :id
|
523
|
+
|
524
|
+
link :self do
|
525
|
+
"http://songs/#{title}"
|
526
|
+
end
|
527
|
+
end
|
528
|
+
```
|
529
|
+
|
530
|
+
Include the `Roar::Representer::XML` engine and get bi-directional XML for your objects.
|
531
|
+
|
532
|
+
```ruby
|
533
|
+
song = Song.new(title: "Roxanne", id: 42)
|
534
|
+
song.extend(XML::SongRepresenter)
|
535
|
+
|
536
|
+
song.to_xml
|
537
|
+
```
|
538
|
+
|
539
|
+
Note that you now use `#to_xml` and `#from_xml`.
|
540
|
+
|
541
|
+
```xml
|
542
|
+
<song>
|
543
|
+
<title>Roxanne</title>
|
544
|
+
<id>42</id>
|
545
|
+
<link rel="self" href="http://songs/Roxanne"/>
|
546
|
+
</song>
|
547
|
+
```
|
548
|
+
|
549
|
+
Please consult the [representable XML documentation](https://github.com/apotonick/representable/#more-on-xml) for all its great features.
|
550
|
+
|
551
|
+
|
552
|
+
## Support
|
553
|
+
|
554
|
+
Questions? Need help? Free 1st Level Support on irc.freenode.org#roar !
|
555
|
+
We also have a [mailing list](https://groups.google.com/forum/?fromgroups#!forum/roar-talk), yiha!
|
556
|
+
|
557
|
+
## License
|
558
|
+
|
559
|
+
Roar is released under the [MIT License](http://www.opensource.org/licenses/MIT).
|