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

Sign up to get free protection for your applications and to get access to all the features.
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
+