strelka 0.0.1.pre.295 → 0.0.1.pre.301

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.
data/Tutorial.rdoc ADDED
@@ -0,0 +1,759 @@
1
+ = Strelka Tutorial
2
+
3
+ == Configuration
4
+
5
+ Strelka uses a library called
6
+ Configurability[http://bitbucket.org/ged/configurability]
7
+ for configuration. It lets you put settings for everything into one
8
+ file, and then distribute the config sections where they belong when
9
+ the file is loaded.
10
+
11
+ It's used by several Strelka plugins, by the Inversion templating system,
12
+ and by the Mongrel2 library. The gem comes with an example config with
13
+ comments; see <tt>{gemdir}/examples/strelka.conf.example</tt>.
14
+
15
+ You can also dump out a config file populated with defaults to get you
16
+ started with the 'strelka' command:
17
+
18
+ $ strelka config > config.yml
19
+
20
+ If you start your apps via the command line tool, the config is loaded
21
+ for you (via the -c option), but you can also do it yourself if you
22
+ prefer launching your apps manually:
23
+
24
+ Strelka.load_config( "myconfig.yml" )
25
+
26
+ It's then available from the <tt>Strelka.config</tt> reader, allowing you
27
+ to reload it if it's changed (or whatever):
28
+
29
+ Strelka.config.reload if Strelka.config.changed?
30
+
31
+ See the {Configurability docs}[http://deveiate.org/code/configurabilty] for
32
+ more details.
33
+
34
+
35
+ == Creating Applications with Plugins
36
+
37
+ As mentioned in the {README page}[rdoc-ref:README.rdoc], our default
38
+ application doesn't afford us many conveniences over using a raw
39
+ <tt>Mongrel2::Handler</tt>. Strelka breaks most of its niceties into a plugin
40
+ system, so you can add only what you require, keeping your apps nice and
41
+ streamlined.
42
+
43
+ The plugins system is contained in the Strelka::PluginLoader mixin, which
44
+ is included in Strelka::App already. This adds the <tt>plugins</tt>
45
+ declarative, which you use from your app to load the plugins you want to use.
46
+
47
+ If you're interested in hooking into the HTTP conversation yourself, or just
48
+ want to factor your common application code up to a reusable plugin, you can
49
+ {Write Your Own Strelka Plugin}[rdoc-ref:Plugins.rdoc], too.
50
+
51
+
52
+ == Routing
53
+
54
+ Rather than checking the <tt>request.path</tt> and supplying elaborate
55
+ conditional dispatching yourself, you can use the +routing+ plugin. This
56
+ provides block style route declarations to your application, that execute their
57
+ blocks when a matching HTTP method and path URI is requested. This is similar
58
+ to {Sinatra's}[http://www.sinatrarb.com/] routing -- if you've used that
59
+ before, this should be familiar territory. It matches routes a bit differently,
60
+ though. Instead of top down, first match wins, Strelka's routing is more
61
+ similar to the routing algorithm that Mongrel2 uses. The longest, most-specific
62
+ route wins, regardless of where it was defined.
63
+
64
+ class HelloWorldApp < Strelka::App
65
+
66
+ plugins :routing
67
+
68
+ # match any GET request
69
+ get do |request|
70
+ request.response.content_type = 'text/plain'
71
+ request.response << 'Hello, World!'
72
+ return request.response
73
+ end
74
+
75
+ # match a GET request starting with '/goodbye'
76
+ get '/goodbye' do |request|
77
+ request.response.content_type = 'text/plain'
78
+ request.response << "Goodbye, cruel World!"
79
+ return request.response
80
+ end
81
+ end
82
+
83
+ The example app above responds only to 'GET' requests. Anything under the
84
+ <tt>/goodbye</tt> URI responds with a departure message, while any other
85
+ request (anywhere in the URI space!) responds with a friendly greeting. You can
86
+ think of this almost as wildcard routing, effectively dividing up the URI space
87
+ into individual handler blocks. A lack of a URI argument is the same thing as
88
+ declaring one with <tt>/</tt>.
89
+
90
+ This introduces some important concepts, as well. All blocks are passed the
91
+ Strelka::HTTPRequest object and should return a Strelka::HTTPResponse object.
92
+ Both the request and response are wrappers around _streams_ (input and output,
93
+ respectively), similar to STDIN and STDOUT in a command-line utility. These
94
+ streams contain the *body* of the request and response, and can be any
95
+ <tt>IO</tt>-like object.
96
+
97
+
98
+ == More Explicit Routing
99
+
100
+ Sometimes you might have an explicit URI mapping already in mind, or just don't
101
+ want the same HTTP resource potentially accessible under different URLs. The
102
+ +routing+ plugin has an option to alter its default matching behavior, and make
103
+ it only match routes that are requested specifically:
104
+
105
+ class HelloWorldApp < Strelka::App
106
+
107
+ plugins :routing
108
+
109
+ # Require exact matching for route paths
110
+ router :exclusive
111
+
112
+ # only match a GET request to '/'
113
+ get do |request|
114
+ request.response.content_type = 'text/plain'
115
+ request.response << 'Hello, World!'
116
+ return request.response
117
+ end
118
+
119
+ # only match a GET request for '/goodbye'
120
+ get '/goodbye' do |request|
121
+ request.response.content_type = 'text/plain'
122
+ request.response << "Goodbye, cruel World!"
123
+ return request.response
124
+ end
125
+ end
126
+
127
+ This application now serves requests to only <tt>/</tt> and
128
+ <tt>/goodbye</tt>. Anything else is met with a <tt>404 NOT FOUND</tt>
129
+ response.
130
+
131
+
132
+ == Setting Content-Types
133
+
134
+ You can, of course, explicitly set the content type for each route as
135
+ we've been doing above. If you'd like to have a fallback if one isn't
136
+ set, Strelka provides an easy way to do so:
137
+
138
+ class HelloWorldApp < Strelka::App
139
+
140
+ plugins :routing
141
+
142
+ # Unless explicitly stated, use a text/plain content-type.
143
+ default_type 'text/plain'
144
+
145
+ get do |request|
146
+ request.response << 'Hello, World!'
147
+ return request.response
148
+ end
149
+
150
+ get '/goodbye' do |request|
151
+ request.response << "Goodbye, cruel World!"
152
+ return request.response
153
+ end
154
+ end
155
+
156
+ Now all content sent will have a <tt>text/plain</tt> content-type, unless
157
+ specifically set to something else in the response.
158
+
159
+
160
+ == Dealing with URI and Query Parameters
161
+
162
+ If you just want to retrieve passed query parameters directly, they are
163
+ accessible as a Hash via the <tt>Request</tt> object.
164
+
165
+ class HelloWorldApp < Strelka::App
166
+
167
+ plugins :routing
168
+
169
+ default_type 'text/plain'
170
+
171
+ get '/' do |request|
172
+ name = request.params['name'] || 'Handsome'
173
+ request.response << "Hello, #{name}!"
174
+ return request.response
175
+ end
176
+ end
177
+
178
+ Try passing <tt>/?name=there</tt> to the handler. By default, no
179
+ validity-checking is done; it'll just return the query arguments as
180
+ Strelka saw them.
181
+
182
+ Strelka offers a +parameters+ plugin that provides a framework for
183
+ describing parameters globally. It manages validation and untainting,
184
+ and you can override the global descriptions on a per-route basis.
185
+
186
+ class HelloWorldApp < Strelka::App
187
+
188
+ plugins :routing, :parameters
189
+
190
+ default_type 'text/plain'
191
+
192
+ param :name, /[[:alpha:]]+/
193
+
194
+ get '/' do |request|
195
+ name = request.params['name'] || 'Handsome'
196
+ request.response << "Hello, #{name}!"
197
+ return request.response
198
+ end
199
+ end
200
+
201
+ Loading the plugin gives you the +param+ method, which you use to
202
+ declare all of the global parameters your application might use. It
203
+ already has a bunch of built in matchers for common things like email
204
+ and hostnames. You can continue using the <tt>params</tt> attribute as a
205
+ Hash, but it now is much smarter than it was before:
206
+
207
+ class HelloWorldApp < Strelka::App
208
+
209
+ plugins :routing, :parameters
210
+
211
+ default_type 'text/plain'
212
+
213
+ param :name, :alpha, :required
214
+ param :email, 'An RFC822 email address'
215
+
216
+ get '/' do |request|
217
+ response = request.response
218
+
219
+ response.puts( request.params.inspect )
220
+
221
+ if request.params.okay?
222
+ response << "Hello, %s!\n" % [ request.params['name'] ]
223
+ else
224
+ response << "Aaahhhh noooaaahhhh!!\n"
225
+ request.params.error_messages.each do |err|
226
+ response.body << " - %s\n" % [ err ]
227
+ end
228
+ end
229
+
230
+ return response
231
+ end
232
+ end
233
+
234
+ Passing the URL <tt>/?name=Bob</tt> should output the following:
235
+
236
+ 1 parameters (1 valid, 0 invalid, 0 missing)
237
+ Hello, Bob!
238
+
239
+ While passing <tt>/?email=what</tt> should display:
240
+
241
+ 1 parameters (0 valid, 1 invalid, 1 missing)
242
+ Aaahhhh noooaaahhhh!!
243
+ - Missing value for 'Name'
244
+ - Invalid value for 'An RFC822 email address'
245
+
246
+ Neat.
247
+
248
+ All this time, we've only been dealing with query parameters. Using the
249
+ +parameters+ plugin also allows params to be part of the route path
250
+ itself. If you have both query AND route parameters in a request, the
251
+ route values win.
252
+
253
+ class HelloWorldApp < Strelka::App
254
+
255
+ plugins :routing, :parameters
256
+
257
+ default_type 'text/plain'
258
+
259
+ param :name, :alpha
260
+ param :email, 'An RFC822 email address'
261
+
262
+ get '/' do |request|
263
+ request.params.override( :email, /\w+@\w+/ )
264
+ name = request.params['name'] || 'Handsome'
265
+ request.response << "Hello, %s!" % [ name ]
266
+ return request.response
267
+ end
268
+
269
+ get '/:name' do |request|
270
+ response = request.response
271
+
272
+ if request.params.okay?
273
+ response.puts "Name: %s" % [ request.params['name'] ]
274
+ else
275
+ response.status = HTTP::BAD_REQUEST
276
+ response.puts( *request.params.error_messages )
277
+ end
278
+
279
+ return response
280
+ end
281
+ end
282
+
283
+ The above example shows how to selectively override the <tt>email</tt>
284
+ parameter for the <tt>/</tt> route, and how to incorporate a parameter
285
+ into a route. There are many, many more options for the param object.
286
+ Please see the {Strelka API documentation}[rdoc-
287
+ ref:Strelka::ParamValidator] for more information.
288
+
289
+ To document:
290
+
291
+ * Named match groups
292
+ * Validating uploaded files (mediatype, size, etc.)
293
+ * Validating non-form entity bodies (YAML, JSON, etc.)
294
+
295
+
296
+ == Accessing Headers
297
+
298
+ The request and response objects both have a headers object that
299
+ provides methods for getting and setting header values. You can use it
300
+ like a Hash:
301
+
302
+ remote_host = request.header['X-Forwarded-For']
303
+ response.header['Content-Type'] = 'image/jpeg'
304
+
305
+ or with a Symbol key (hyphens become underscores):
306
+
307
+ remote_host = request.header[:x_forwarded_for]
308
+ response.header[:content_type] = 'application/pdf'
309
+
310
+ You can also access it using struct-like methods, with the same pattern
311
+ as with Symbol keys:
312
+
313
+ remote_host = request.header.x_forwarded_for
314
+ response.header.content_type = 'text/html'
315
+
316
+ Keep in mind that some headers can appear multiple times, so what you
317
+ get back could be an Array, too.
318
+
319
+
320
+ == Setting Response Status
321
+
322
+ Responses start out with a <tt>204 No Content</tt> status, and will
323
+ automatically switch to <tt>200 OK</tt> if you add a body to it.
324
+
325
+ You can, of course, set the status yourself:
326
+
327
+ response.status = HTTP::NOT_FOUND
328
+
329
+ If you set it directly, however, the response will still go back through
330
+ all of your loaded plugins, which is probably not what you want. In that
331
+ case you can finish a request early using the {finish_with helper}[rdoc-
332
+ ref:Strelka::App#finish_with]:
333
+
334
+ finish_with HTTP::NOT_FOUND, "That user doesn't exist."
335
+
336
+ Using <tt>finish_with</tt> stops additional processing immediately and returns
337
+ a response with the specified status and message. You can also include
338
+ additional headers:
339
+
340
+ new_location = "http://example.com/somewhere/else"
341
+ finish_with HTTP::REDIRECT,
342
+ "That resource has moved here: #{new_location}.",
343
+ headers: { location: new_location }
344
+
345
+
346
+ == Using Templates
347
+
348
+ Most web frameworks come with some kind of templating built in, but
349
+ Strelka doesn't have any preconceived assumptions about what you might
350
+ want to use for your applications. As long as your templates implement
351
+ <tt>#to_s</tt>, you can set them as the response body and your app will
352
+ work fine:
353
+
354
+ require 'erubis'
355
+
356
+ class HelloWorldApp < Strelka::App
357
+
358
+ plugins :routing
359
+
360
+ default_type 'text/html'
361
+
362
+ get '/' do |request|
363
+ response = request.response
364
+ template = Erubis::Eruby.load_file( 'template1.rhtml' )
365
+ response.body = template.evaluate( :greeting => "Why hello!" )
366
+ return response
367
+ end
368
+
369
+ end
370
+
371
+ Strelka comes with a <tt>:templating</tt> plugin that provides your application
372
+ with the ability to use the
373
+ {Inversion templating system}[http://deveiate.org/code/Inversion-manual/] to
374
+ build a response with minimal fuss.
375
+
376
+ class HelloWorldApp < Strelka::App
377
+
378
+ plugins :routing, :templating
379
+
380
+ templates :index => 'index.tmpl'
381
+
382
+ default_type 'text/html'
383
+
384
+ get '/' do |request|
385
+ tmpl = template( :index )
386
+ tmpl.greeting = "Why hello!"
387
+ return tmpl
388
+ end
389
+ end
390
+
391
+ Using Inversion, you can optionally wrap content in a global look and
392
+ feel via a layout template. This is accomplished by simply declaring a
393
+ layout template, which should contain a <tt>body</tt> attribute. That
394
+ attribute expands to the current response template.
395
+
396
+ class HelloWorldApp < Strelka::App
397
+
398
+ plugins :routing, :templating
399
+
400
+ layout 'layout.tmpl'
401
+ templates :index => 'index.tmpl'
402
+
403
+ default_type 'text/html'
404
+
405
+ get '/' do |request|
406
+ tmpl = template( :index )
407
+ tmpl.greeting = "Why hello!"
408
+ return tmpl
409
+ end
410
+ end
411
+
412
+ In the above example, the Inversion index template would look as such:
413
+
414
+ <!-- index.tmpl -->
415
+ <?attr greeting ?>
416
+
417
+ and then the layout might look like:
418
+
419
+ <!-- layout.tmpl -->
420
+ <html>
421
+ <body>
422
+ <?attr body ?>
423
+ </body>
424
+ </html>
425
+
426
+ If you need to set some stuff on the response object, you can also set
427
+ the template as the response body with the same effect:
428
+
429
+ class HelloWorldApp < Strelka::App
430
+
431
+ plugins :routing, :templating
432
+
433
+ layout 'layout.tmpl'
434
+ templates :index => 'index.tmpl'
435
+
436
+ default_type 'text/html'
437
+
438
+ get '/' do |request|
439
+ tmpl = template( :index )
440
+ tmpl.greeting = "Why hello!"
441
+
442
+ response = request.response
443
+ response.status = HTTP::CREATED
444
+ response.body = tmpl
445
+
446
+ return response
447
+ end
448
+ end
449
+
450
+ On the other hand, if your application doesn't need to set attributes on
451
+ the template *or* the response, you can automate the template loading
452
+ and response by returning the name of the template (as a Symbol)
453
+ instead:
454
+
455
+ class HelloWorldApp < Strelka::App
456
+
457
+ plugins :routing, :templating
458
+
459
+ layout 'layout.tmpl'
460
+ templates :index => 'index.tmpl'
461
+
462
+ default_type 'text/html'
463
+
464
+ get '/' do |request|
465
+ return :index
466
+ end
467
+ end
468
+
469
+ Sometimes you want to have a layout, but there's one or two responses
470
+ that you don't want to be wrapped. You can accomplish this just by
471
+ rendering the template immediately so the response body is just a
472
+ String:
473
+
474
+ class HelloWorldApp < Strelka::App
475
+
476
+ plugins :routing, :templating
477
+
478
+ layout 'layout.tmpl'
479
+ templates :robots => 'robots.txt.tmpl'
480
+
481
+ default_type 'text/html'
482
+
483
+ get '/robots.txt' do |request|
484
+ response = request.response
485
+ response.content_type = 'text/plain'
486
+ response.body = template( :robots ).render
487
+ return response
488
+ end
489
+ end
490
+
491
+ Response templates also have some attributes automatically set on them
492
+ by the <tt>:templating</tt> plugin:
493
+
494
+ [request] The current Strelka::HTTPRequest object.
495
+ [app] The application object.
496
+ [strelka_version] The current Strelka version string.
497
+ [mongrel2_version] The current Ruby-Mongrel2 version string.
498
+ [route] If you're using the <tt>:routing</tt> plugin, this will be
499
+ set to the routing information for the matched route.
500
+
501
+ Note that if you're using a layout template, you'll need to use the
502
+ <tt>import</tt> tag to use them in the body template:
503
+
504
+ <?import request, app, route ?>
505
+
506
+
507
+ == Filtering Every Request and/or Response
508
+
509
+ Sometimes there are actions you want to take before requests are
510
+ handled, or after the response is built. To do that, you can use the
511
+ {:filters}[rdoc-ref:Strelka::App::Filters] plugin.
512
+
513
+ You enable that the same way as with other plugins:
514
+
515
+ plugin :filters
516
+
517
+ That gives you the <tt>filter</tt> directive. You can declare
518
+ <tt>:request</tt> filters, <tt>:response</tt> filters, or filters that
519
+ apply to <tt>:both</tt> requests and responses.
520
+
521
+ For example:
522
+
523
+ ### Make various configuration settings available to templates.
524
+ ###
525
+ filter :request do |request|
526
+ # Provide the ability to hook/display other stuff if running in
527
+ # development.
528
+ request.notes[ :devmode ] = MyLibrary.dev?
529
+
530
+ # The default contact email address.
531
+ request.notes[ :contact ] = MyLibrary.contact_email
532
+
533
+ # Yo, what time is it?
534
+ request.notes[ :now ] = Time.now
535
+ end
536
+
537
+ ### Modify outgoing headers on all responses to include library
538
+ ### version info.
539
+ ###
540
+ filter :response do |response|
541
+ response.headers.x_libraryversion = MyLibrary.version_string
542
+ response.headers.x_elapsed = Time.now - response.request.notes[ :now ]
543
+ end
544
+
545
+ See the docs for Strelka::App::Filters for more details.
546
+
547
+
548
+ == Altering Error Display
549
+
550
+ Strelka provides basic error-handling, turning any exception that is
551
+ raised from your application into a 500 response, etc., but you'll
552
+ probably want to provide something prettier and/or override some
553
+ response types.
554
+
555
+ The {:errors}[rdoc-ref:Strelka::App::Errors] plugin can help you with
556
+ this; enable it the usual way:
557
+
558
+ plugin :errors
559
+
560
+ It provides your application with an {on_status}[rdoc-
561
+ ref:Strelka::App::Errors.on_status] declarative that you can use to
562
+ provide handlers for particular status codes, or ranges of status codes:
563
+
564
+ # Handle only status 400 errors
565
+ on_status HTTP::BAD_REQUEST do |response, status_info|
566
+ # Do something on 400 errors
567
+ end
568
+
569
+ # Handle any other error in the 4xx range
570
+ on_status 400..499 do |response, status_info|
571
+ # Do something on 4xx errors
572
+ end
573
+
574
+ See the {API docs}[rdoc-ref:Strelka::App::Errors] for more details.
575
+
576
+
577
+ == Sessions
578
+
579
+ When you need some place to store a bit of state between requests,
580
+ there's the {:sessions plugin}[rdoc-ref:Strelka::Apps::Sessions]. Since
581
+ sessions have all kinds of strategies for server-side and client-side
582
+ storage, the Strelka sessions plugin is really a mini-framework for
583
+ storing serialized data. Strelka comes with a few basic examples to get
584
+ you started, but in all likelihood, you'll want to create your own
585
+ session type, or use a third-party one that fits into your environment.
586
+
587
+ The session plugin to use is set via the config file:
588
+
589
+ # Use built in (in-process) session storage.
590
+ sessions:
591
+ type: default
592
+
593
+ And then the plugin itself is configured via its own section:
594
+
595
+ # Configuration for the built in session provider.
596
+ defaultsession:
597
+ cookie_name: "session-demo"
598
+ cookie_options:
599
+ expires: +5m
600
+
601
+ The 'default' session type uses an in-memory Hash to store session data,
602
+ and cookies to track the session ID.
603
+
604
+ Strelka also comes with a simple database storage session plugin, that
605
+ supports any database backend that Sequel[http://sequel.rubyforge.org/]
606
+ does. Its configuration looks similar. Here's an example that writes
607
+ session information to a SQLite[http://www.sqlite.org/] database:
608
+
609
+ # Use the database session storage.
610
+ sessions:
611
+ type: db
612
+
613
+ # Configuration for the database session provider.
614
+ dbsession:
615
+ connect: sqlite://sessions.db
616
+ cookie_name: "session-demo"
617
+ cookie_options:
618
+ expires: +5m
619
+
620
+ See the API docs for Strelka::App::Sessions for more.
621
+
622
+
623
+ == Authentication and Authorization
624
+
625
+ When you want to guard all or part of your application behind an
626
+ authentication layer, the {:auth plugin}[rdoc-ref:Strelka::App::Auth]
627
+ can help.
628
+
629
+ As with the :sessions plugin, Strelka only comes with a basic plugin
630
+ just to get you started, as you're likely to want to use one that is
631
+ particular to your environment.
632
+
633
+ It, too, is configured via a section of the config file:
634
+
635
+ # Use the Basic authentication provider for
636
+ # any routes that require user validation.
637
+ auth:
638
+ provider: basic
639
+
640
+ and then the plugin has its own section:
641
+
642
+ # Configuration for the Basic auth provider.
643
+ basicauth:
644
+ realm: Examples
645
+ users:
646
+ ged: "dn44fsIrKF6KmBw0im4GIJktSpM="
647
+ jblack: "1pAnQNSVtpL1z88QwXV4sG8NMP8="
648
+ kmurgen: "MZj9+VhZ8C9+aJhmwp+kWBL76Vs="
649
+
650
+
651
+ Protecting all routes in a handler is the default behavior when the
652
+ plugin is loaded:
653
+
654
+ class HelloWorldApp < Strelka::App
655
+
656
+ plugins :routing, :auth
657
+
658
+ default_type 'text/html'
659
+
660
+ get do |request|
661
+ request.response.content_type = 'text/plain'
662
+ request.response << "Hello, %s!" % [ request.authenticated_user ]
663
+ return request.response
664
+ end
665
+ end
666
+
667
+ You can be specific about what routes are protected also:
668
+
669
+ class HelloWorldApp < Strelka::App
670
+
671
+ plugins :routing, :auth
672
+
673
+ # Only require authentication for /private
674
+ require_auth_for '/private'
675
+
676
+ default_type 'text/html'
677
+
678
+ get '/public' do |request|
679
+ request.response.content_type = 'text/plain'
680
+ request.response << "Hello, Anonymous!"
681
+ return request.response
682
+ end
683
+
684
+ get '/private' do |request|
685
+ request.response.content_type = 'text/plain'
686
+ request.response << "Hello, %s!" % [ request.authenticated_user ]
687
+ return request.response
688
+ end
689
+ end
690
+
691
+ See the API docs for Strelka::App::Auth for further details on usage,
692
+ how to integrate it with your application, applying authorization
693
+ permissions, and how to create your own plugin for your environment.
694
+
695
+
696
+ == HTTP Content Negotiation
697
+
698
+ Nowadays, when you write a web application, you're more than likely writing
699
+ one or more REST (or at least HTTP-based) services as part of it. Even if
700
+ you're not, you might want to support
701
+ {HTTP Content Negotation}[http://tools.ietf.org/html/rfc2616#section-12]
702
+ for your regular content, too.
703
+
704
+ The {:negotiation plugin}[rdoc-ref:Strelka::App::Negotiation] allows you
705
+ to negotiate with the client via <tt>Allow*</tt> headers to determine
706
+ the best mediatype, character encoding, natural language, and transport
707
+ encoding.
708
+
709
+ Here's an example of how you might support several different formats for
710
+ an extremely simple 'User' service:
711
+
712
+ require 'user'
713
+
714
+ class UserService < Strelka::App
715
+
716
+ plugins :routing, :negotiation, :parameters
717
+
718
+ # Declare the ID parameter
719
+ param :id, :integer, "User ID"
720
+
721
+ # GET /users -- collection method
722
+ get do |request|
723
+ collection = User.all
724
+ response = request.response
725
+ response.for( :json, :yaml ) { collection }
726
+ response.for( :text ) do
727
+ str = "Users:"
728
+ collection.each do |user|
729
+ str << user.to_s << "\n"
730
+ end
731
+ str
732
+ end
733
+
734
+ return response
735
+ end
736
+
737
+ # GET /users/{id} -- fetch a single record
738
+ get '/:id' do |request|
739
+ id = request.params[:id]
740
+ user = User[ id ] or finish_with( HTTP::NOT_FOUND, "no such user #{id}" )
741
+
742
+ response = request.response
743
+ response.for( :json, :yaml ) { user }
744
+ response.for( :text ) { user.to_s }
745
+
746
+ return response
747
+ end
748
+
749
+ # ...and so on...
750
+
751
+ end # class UserService
752
+
753
+ The above example only demonstrates content negotiation, but there's a bunch of
754
+ additional stuff you can do with this plugin. See the API docs for
755
+ Strelka::App::Negotiation for all the goods.
756
+
757
+
758
+ == REST Services
759
+