roar 0.12.1 → 0.12.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 29d7b45c0c70d694a009c07d504cdb64527a67dd
4
- data.tar.gz: 3b4e5c1d025541be5de8e5b0df42cf01052f843a
3
+ metadata.gz: 04161ea9b3e943c2369825c6a3c426cfae97550d
4
+ data.tar.gz: f7a7a96c45646273d7355938becde08c0549d8b1
5
5
  SHA512:
6
- metadata.gz: b9e45c7ec240bce207c05a8165fd03d8a35c770ee60a643c5fe07a5feff5e99ab646b3873efa619e3d732cb9aec9a079d7aa843ac685de3ec75cd50c0f20dca5
7
- data.tar.gz: cfde908ba1165bb30fa43dc8a22b8d5de7bff6092ccf38d0eb906bd4ff04ca170a44726a9a3ff597e91c8a41deea83cd29d4eac6edcc8b48c509d6cce8b7dba5
6
+ metadata.gz: 766eddbf40e6931caaf003a895598799c1d40efea0ba4854a1938995c0a7c0a41b96cfe1e69593c9a89538b9426305aff2f34dcd617a60a95f14840398296e54
7
+ data.tar.gz: ec8585e4bc27fb429895db8d2026c70aade0b360d5b6d741d4d67c7eddc0306194947514061f8f1fe6361822ddceb5e1370b6ff671430c9628b5af5781c791e4
@@ -1,3 +1,7 @@
1
+ # 0.12.2
2
+
3
+ * Fix a bug where hyperlinks from nested objects weren't rendered in XML.
4
+
1
5
  # 0.12.1
2
6
 
3
7
  Allow representable >= 1.6.
@@ -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).