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 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).