roda-cj 0.9.6 → 1.0.0

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/Rakefile CHANGED
@@ -34,7 +34,7 @@ rescue LoadError
34
34
  end
35
35
 
36
36
  RDOC_OPTS = RDOC_DEFAULT_OPTS + ['--main', 'README.rdoc']
37
- RDOC_FILES = %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb"
37
+ RDOC_FILES = %w"README.rdoc CHANGELOG MIT-LICENSE lib/**/*.rb" + Dir["doc/*.rdoc"] + Dir['doc/release_notes/*.txt']
38
38
 
39
39
  rdoc_task_class.new do |rdoc|
40
40
  rdoc.rdoc_dir = "rdoc"
@@ -0,0 +1,329 @@
1
+ = New Plugins
2
+
3
+ * A csrf plugin has been added for CSRF prevention, using
4
+ Rack::Csrf. It also adds helper methods for views such as
5
+ csrf_tag.
6
+
7
+ * A symbol_matchers plugin has been added, for customizing
8
+ the regexps used per symbol. This also affects the use
9
+ of embedded colons in strings. This supports the following
10
+ symbol regexps by default:
11
+
12
+ :d :: (\d+), a decimal segment
13
+ :format :: (?:\.(\w+))?, an optional format/extension
14
+ :opt :: (?:\/([^\/]+))?, an optional segment
15
+ :optd :: (?:\/(\d+))?, an optional decimal segment
16
+ :rest :: (.*), all remaining characters, if any
17
+ :w :: (\w+), a alphanumeric segment
18
+
19
+ This allows you to write code such as:
20
+
21
+ plugin :symbol_matchers
22
+
23
+ route do |r|
24
+ r.is "track/:d" do
25
+ end
26
+ end
27
+
28
+ And have it only match routes such as /track/123, not
29
+ /track/abc.
30
+
31
+ Note that :opt, :optd, and :format are only going to make sense
32
+ when used as embedded colons in strings, due to how segment matching
33
+ works.
34
+
35
+ You can add your own symbol matchers using the symbol_matcher
36
+ class method:
37
+
38
+ plugin :symbol_matchers
39
+ symbol_matcher :slug, /([\w-]+)/
40
+
41
+ route do |r|
42
+ r.on :slug do
43
+ end
44
+ end
45
+
46
+ * A symbol_views plugin has been added, which allows match blocks to
47
+ return symbols, which are interpreted as template names:
48
+
49
+ plugin :symbol_views
50
+
51
+ route do |r|
52
+ :template_name # same as view :template_name
53
+ end
54
+
55
+ * A json plugin has been added, which allows match blocks to return
56
+ arrays or hashes, and uses a JSON version of them as the response
57
+ body:
58
+
59
+ plugin :json
60
+
61
+ route do |r|
62
+ {'a'=>[1,2,3]} # response: {"a":[1,2,3]}
63
+ end
64
+
65
+ This also sets the Content-Type of the response to application/json.
66
+
67
+ To convert additional object types to JSON, you can modify
68
+ json_response_classes:
69
+
70
+ plugin :json
71
+ json_response_classes << Sequel::Model
72
+
73
+ * A view_subdirs plugin has been added for setting a default
74
+ subdirectory to use for views:
75
+
76
+ Roda.route do |r|
77
+ r.on "admin" do
78
+ set_view_subdir "admin"
79
+
80
+ r.is do
81
+ view "index" # uses admin/index view
82
+ end
83
+ end
84
+ end
85
+
86
+ * A render_each plugin has been added, for rendering the same
87
+ template for multiple objects, and returning the concatenation
88
+ of all of the output:
89
+
90
+ <%= render_each([1,2,3], 'number') %>
91
+
92
+ This renders the number template 3 times. Each time the template
93
+ is rendered, a local variable named number will be present with
94
+ the current entry in the enumerable. You can control the name of
95
+ the local variable using the :local option:
96
+
97
+ <%= render_each([1,2,3], 'number', :local=>:n) %>
98
+
99
+ * A content_for plugin has been added, for storing content in one
100
+ template and retrieving that content in a different template (such
101
+ as the layout). To set content, you call content_for with a block:
102
+
103
+ <% content_for :foo do %>
104
+ content for foo
105
+ <% end %>
106
+
107
+ To retrieve content, you call content_for without a block:
108
+
109
+ <%= content_for :foo %>
110
+
111
+ This plugin probably only works when using erb templates.
112
+
113
+ * A not_allowed plugin has been added, for automatically returning 405
114
+ Method Not Allowed responses when a route is handled for a different
115
+ request method than the one used. For this routing tree:
116
+
117
+ plugin :not_allowed
118
+
119
+ route do |r|
120
+ r.get "foo" do
121
+ end
122
+ end
123
+
124
+ If you submit a POST /foo request, it will return a 405 error
125
+ instead of a 404 error.
126
+
127
+ This also handles cases when multiple methods are supported for
128
+ a single path, so for this routing tree:
129
+
130
+ route do |r|
131
+ r.is "foo" do
132
+ r.get do
133
+ end
134
+ r.post do
135
+ end
136
+ end
137
+ end
138
+
139
+ If you submit a DELETE /foo request, it will return a 405 error
140
+ instead of a 404 error.
141
+
142
+ * A head plugin has been added, automatically handling HEAD requests
143
+ the same as GET requests, except returning an empty body. So for
144
+ this routing tree:
145
+
146
+ plugin :head
147
+
148
+ route do |r|
149
+ r.get "foo" do
150
+ end
151
+ end
152
+
153
+ A request for HEAD /foo will return a 200 result instead of a 404
154
+ error.
155
+
156
+ * A backtracking_array plugin has been added, which makes matching
157
+ backtrack to the next entry in an array if a later matcher fails.
158
+ For example, the following code does not match /foo/bar by
159
+ default in Roda:
160
+
161
+ r.is ['foo', 'foo/bar'] do
162
+ end
163
+
164
+ This is because the 'foo' entry in the array matches, so the
165
+ array matches. However, after the array is matched, the terminal
166
+ matcher added by r.is fails to match. That causes the routing
167
+ method not to match the request, so the match block is not called.
168
+
169
+ With the backtracking_array plugin, failures of later matchers after
170
+ an array matcher backtrack so the next entry in the array is tried.
171
+
172
+ * A per_thread_caching plugin has been added, allowing you to change
173
+ from a thread-safe shared cache to a per-thread cache, which may
174
+ be faster on alternative ruby implementations, at the cost of
175
+ additional memory usage.
176
+
177
+ = New Features
178
+
179
+ * The hash_matcher class method has been added to make it easier to
180
+ define custom hash matchers:
181
+
182
+ hash_matcher(:foo) do |v|
183
+ self['foo'] == v
184
+ end
185
+
186
+ route do |r|
187
+ r.on :foo=>'bar' do
188
+ # matches when param foo has value bar
189
+ end
190
+ end
191
+
192
+ * An r.root routing method has been added for handling GET
193
+ requests where the current path is /. This is basically
194
+ a faster and simpler version of r.get "", except it does
195
+ not consume the / from the path.
196
+
197
+ * The r.halt method now works without an argument, in which
198
+ case it uses the current response.
199
+
200
+ * The r.redirect method now works without an argument for non-GET
201
+ requests, redirecting to the current path.
202
+
203
+ * An :all hash matcher has been added, which takes an array and
204
+ matches only if all of the elements match. This is mainly
205
+ designed for usage inside an array matcher, so:
206
+
207
+ r.on ["foo", {:all=>["bar", :id]}] do
208
+ end
209
+
210
+ will match either /foo or /bar/123, but not /bar.
211
+
212
+ * The render plugin's view method now accepts a :content option,
213
+ in which case it uses the content directly without running it
214
+ through the template engine. This is useful if you have
215
+ arbitrary content you want rendered inside the layout.
216
+
217
+ * The render plugin now accepts an :escape option, in which case
218
+ it will automatically set the default :engine_class for erb
219
+ templates to an Erubis::EscapedEruby subclass. This changes the
220
+ behavior of erb templates such that:
221
+
222
+ <%= '<escaped>' %> # &lt;escaped&gt;
223
+ <%== '<not escaped>' %> # <not escaped>
224
+
225
+ This makes it easier to protect against XSS attacks in your
226
+ templates, as long as you only use <%== %> for content that has
227
+ already been escaped.
228
+
229
+ Note that similar behavior is available in Erubis by default,
230
+ using the :opts=>{:escape_html=>true} render option, but that
231
+ doesn't handle postfix conditionals in <%= %> tags.
232
+
233
+ * The multi_route plugin now has an r.multi_route method, which
234
+ will attempt to dispatch to one of the named routes based on
235
+ first segment in the path. So this routing tree:
236
+
237
+ plugin :multi_route
238
+
239
+ route "a" do |r|
240
+ r.is "c" do
241
+ "e"
242
+ end
243
+ end
244
+ route "b" do |r|
245
+ r.is "d" do
246
+ "f"
247
+ end
248
+ end
249
+
250
+ route do |r|
251
+ r.multi_route
252
+ end
253
+
254
+ will return "e" for /a/c and "f" for /b/d.
255
+
256
+ * Plugins can now override request and response class methods
257
+ using RequestClassMethods and ResponseClassMethods modules.
258
+
259
+ = Optimizations
260
+
261
+ * String, hash, and symbol matchers are now much faster by caching
262
+ the underlying regexp.
263
+
264
+ * String, hash, and symbol matchers are now faster by using a
265
+ regexp positive lookahead assertion instead of an additional
266
+ capture.
267
+
268
+ * Terminal matching in the r.is, r.get, and r.post routing methods
269
+ is now faster, as it does not use a hash matcher internally.
270
+
271
+ * The routing methods are now faster by reducing the number of
272
+ Array objects created.
273
+
274
+ * Calling routing methods without arguments is now faster.
275
+
276
+ * The r.get method is now faster by reducing the number of string
277
+ allocations.
278
+
279
+ * Many request methods are faster by reducing the number of
280
+ method calls used.
281
+
282
+ * Template caching no longer uses a mutex on MRI, since one is
283
+ not needed for thread safety there.
284
+
285
+ = Other Improvements
286
+
287
+ * The flash plugin now implements its own flash hash instead of
288
+ using sinatra-flash. It is now slightly faster and handles nil
289
+ keys in #keep and #discard.
290
+
291
+ * Roda's version is now stored in roda/version.rb so that it can be
292
+ required without requiring Roda itself.
293
+
294
+ = Backwards Compatibility
295
+
296
+ * The multi_route plugin's route instance method has been changed
297
+ to a request method. So the new usage is:
298
+
299
+ plugin :multi_route
300
+
301
+ route "a" do |r|
302
+ end
303
+
304
+ route do |r|
305
+ r.route "a" # instead of: route "a"
306
+ end
307
+
308
+ * The session key used for the flash hash in the flash plugin is
309
+ now :_flash, not :flash.
310
+
311
+ * The :extension matcher now longer forces a terminal match, use
312
+ one of the routing methods that forces a terminal match if you
313
+ want that behavior.
314
+
315
+ * The :term hash matcher has been removed.
316
+
317
+ * The r.consume private method now takes the exact regexp to use
318
+ to search the current path, it no longer enforces a preceeding
319
+ slash and that the match end on a segment boundary.
320
+
321
+ * Dynamically constructing match patterns is now a potential
322
+ memory leak due to them being cached. So you shouldn't do
323
+ things like:
324
+
325
+ r.on r['param'] do
326
+ end
327
+
328
+ * Many private routing methods were changed or removed, if you were
329
+ using them, you'll probably need to update your code.
@@ -51,9 +51,10 @@ class Roda
51
51
  @roda_class = ::Roda
52
52
  end
53
53
 
54
- @builder = ::Rack::Builder.new
54
+ @app = nil
55
55
  @middleware = []
56
56
  @opts = {}
57
+ @route_block = nil
57
58
 
58
59
  # Module in which all Roda plugins should be stored. Also contains logic for
59
60
  # registering and loading plugins.
@@ -75,7 +76,9 @@ class Roda
75
76
  end
76
77
 
77
78
  # Register the given plugin with Roda, so that it can be loaded using #plugin
78
- # with a symbol. Should be used by plugin files.
79
+ # with a symbol. Should be used by plugin files. Example:
80
+ #
81
+ # Roda::RodaPlugins.register_plugin(:plugin_name, PluginModule)
79
82
  def self.register_plugin(name, mod)
80
83
  @plugins[name] = mod
81
84
  end
@@ -92,6 +95,9 @@ class Roda
92
95
  # The settings/options hash for the current class.
93
96
  attr_reader :opts
94
97
 
98
+ # The route block that this class uses.
99
+ attr_reader :route_block
100
+
95
101
  # Call the internal rack application with the given environment.
96
102
  # This allows the class itself to be used as a rack application.
97
103
  # However, for performance, it's better to use #app to get direct
@@ -104,21 +110,30 @@ class Roda
104
110
  # block, so that using a hash key in a request match method will
105
111
  # call the block. The block should return nil or false to not
106
112
  # match, and anything else to match.
113
+ #
114
+ # class App < Roda
115
+ # hash_matcher(:foo) do |v|
116
+ # self['foo'] == v
117
+ # end
118
+ #
119
+ # route do
120
+ # r.on :foo=>'bar' do
121
+ # # matches when param foo has value bar
122
+ # end
123
+ # end
124
+ # end
107
125
  def hash_matcher(key, &block)
108
126
  request_module{define_method(:"match_#{key}", &block)}
109
127
  end
110
128
 
111
- # When inheriting Roda, setup a new rack app builder, copy the
112
- # default middleware and opts into the subclass, and set the
113
- # request and response classes in the subclasses to be subclasses
114
- # of the request and responses classes in the parent class. This
115
- # makes it so child classes inherit plugins from their parent,
116
- # but using plugins in child classes does not affect the parent.
129
+ # When inheriting Roda, copy the shared data into the subclass,
130
+ # and setup the request and response subclasses.
117
131
  def inherited(subclass)
118
132
  super
119
- subclass.instance_variable_set(:@builder, ::Rack::Builder.new)
120
133
  subclass.instance_variable_set(:@middleware, @middleware.dup)
121
134
  subclass.instance_variable_set(:@opts, opts.dup)
135
+ subclass.instance_variable_set(:@route_block, @route_block)
136
+ subclass.send(:build_rack_app)
122
137
 
123
138
  request_class = Class.new(self::RodaRequest)
124
139
  request_class.roda_class = subclass
@@ -133,6 +148,9 @@ class Roda
133
148
  # Load a new plugin into the current class. A plugin can be a module
134
149
  # which is used directly, or a symbol represented a registered plugin
135
150
  # which will be required and then used.
151
+ #
152
+ # Roda.plugin PluginModule
153
+ # Roda.plugin :csrf
136
154
  def plugin(mixin, *args, &block)
137
155
  if mixin.is_a?(Symbol)
138
156
  mixin = RodaPlugins.load_plugin(mixin)
@@ -168,24 +186,58 @@ class Roda
168
186
 
169
187
  # Include the given module in the request class. If a block
170
188
  # is provided instead of a module, create a module using the
171
- # the block.
189
+ # the block. Example:
190
+ #
191
+ # Roda.request_module SomeModule
192
+ #
193
+ # Roda.request_module do
194
+ # def description
195
+ # "#{request_method} #{path_info}"
196
+ # end
197
+ # end
198
+ #
199
+ # Roda.route do |r|
200
+ # r.description
201
+ # end
172
202
  def request_module(mod = nil, &block)
173
203
  module_include(:request, mod, &block)
174
204
  end
175
205
 
176
206
  # Include the given module in the response class. If a block
177
207
  # is provided instead of a module, create a module using the
178
- # the block.
208
+ # the block. Example:
209
+ #
210
+ # Roda.response_module SomeModule
211
+ #
212
+ # Roda.response_module do
213
+ # def error!
214
+ # self.status = 500
215
+ # end
216
+ # end
217
+ #
218
+ # Roda.route do |r|
219
+ # response.error!
220
+ # end
179
221
  def response_module(mod = nil, &block)
180
222
  module_include(:response, mod, &block)
181
223
  end
182
224
 
183
- # Setup route definitions for the current class, and build the
184
- # rack application using the stored middleware.
225
+ # Setup routing tree for the current Roda application, and build the
226
+ # underlying rack application using the stored middleware. Requires
227
+ # a block, which is yielded the request. By convention, the block
228
+ # argument should be named +r+. Example:
229
+ #
230
+ # Roda.route do |r|
231
+ # r.root do
232
+ # "Root"
233
+ # end
234
+ # end
235
+ #
236
+ # This should only be called once per class, and if called multiple
237
+ # times will overwrite the previous routing.
185
238
  def route(&block)
186
- @middleware.each{|a, b| @builder.use(*a, &b)}
187
- @builder.run lambda{|env| new.call(env, &block)}
188
- @app = @builder.to_app
239
+ @route_block = block
240
+ build_rack_app
189
241
  end
190
242
 
191
243
  # A new thread safe cache instance. This is a method so it can be
@@ -195,13 +247,26 @@ class Roda
195
247
  end
196
248
 
197
249
  # Add a middleware to use for the rack application. Must be
198
- # called before calling #route.
250
+ # called before calling #route to have an effect. Example:
251
+ #
252
+ # Roda.use Rack::Session::Cookie, :secret=>ENV['secret']
199
253
  def use(*args, &block)
200
254
  @middleware << [args, block]
255
+ build_rack_app
201
256
  end
202
257
 
203
258
  private
204
259
 
260
+ # Build the rack app to use
261
+ def build_rack_app
262
+ if block = @route_block
263
+ builder = Rack::Builder.new
264
+ @middleware.each{|a, b| builder.use(*a, &b)}
265
+ builder.run lambda{|env| new.call(env, &block)}
266
+ @app = builder.to_app
267
+ end
268
+ end
269
+
205
270
  # Backbone of the request_module and response_module support.
206
271
  def module_include(type, mod)
207
272
  if type == :response
@@ -234,25 +299,34 @@ class Roda
234
299
 
235
300
  # Create a request and response of the appopriate
236
301
  # class, the instance_exec the route block with
237
- # the request, handling any halts.
302
+ # the request, handling any halts. This is not usually
303
+ # called directly.
238
304
  def call(env, &block)
239
305
  @_request = self.class::RodaRequest.new(self, env)
240
306
  @_response = self.class::RodaResponse.new
241
307
  _route(&block)
242
308
  end
243
309
 
244
- # The environment for the current request.
310
+ # The environment hash for the current request. Example:
311
+ #
312
+ # env['REQUEST_METHOD'] # => 'GET'
245
313
  def env
246
314
  request.env
247
315
  end
248
316
 
249
317
  # The class-level options hash. This should probably not be
250
- # modified at the instance level.
318
+ # modified at the instance level. Example:
319
+ #
320
+ # Roda.plugin :render
321
+ # Roda.route do |r|
322
+ # opts[:render_opts].inspect
323
+ # end
251
324
  def opts
252
325
  self.class.opts
253
326
  end
254
327
 
255
328
  # The instance of the request class related to this request.
329
+ # This is the same object yielded by Roda.route.
256
330
  def request
257
331
  @_request
258
332
  end
@@ -363,7 +437,12 @@ class Roda
363
437
  end
364
438
 
365
439
  # As request routing modifies SCRIPT_NAME and PATH_INFO, this exists
366
- # as a helper method to get the full request of the path info.
440
+ # as a helper method to get the full path of the request.
441
+ #
442
+ # r.env['SCRIPT_NAME'] = '/foo'
443
+ # r.env['PATH_INFO'] = '/bar'
444
+ # r.full_path_info
445
+ # # => '/foo/bar'
367
446
  def full_path_info
368
447
  "#{@env[SCRIPT_NAME]}#{@env[PATH_INFO]}"
369
448
  end
@@ -371,13 +450,20 @@ class Roda
371
450
  # Immediately stop execution of the route block and return the given
372
451
  # rack response array of status, headers, and body. If no argument
373
452
  # is given, uses the current response.
453
+ #
454
+ # r.halt [200, {'Content-Type'=>'text/html'}, ['Hello World!']]
455
+ #
456
+ # response.status = 200
457
+ # response['Content-Type'] = 'text/html'
458
+ # response.write 'Hello World!'
459
+ # r.halt
374
460
  def halt(res=response.finish)
375
461
  throw :halt, res
376
462
  end
377
463
 
378
- # Whether this request is a get request. Similar to the default
379
- # Rack::Request get? method, but can be overridden without changing
380
- # rack's behavior.
464
+ # Optimized method for whether this request is a +GET+ request.
465
+ # Similar to the default Rack::Request get? method, but can be
466
+ # overridden without changing rack's behavior.
381
467
  def is_get?
382
468
  @env[REQUEST_METHOD] == GET_REQUEST_METHOD
383
469
  end
@@ -393,12 +479,56 @@ class Roda
393
479
 
394
480
  # Show information about current request, including request class,
395
481
  # request method and full path.
482
+ #
483
+ # r.inspect
484
+ # # => '#<Roda::RodaRequest GET /foo/bar>'
396
485
  def inspect
397
486
  "#<#{self.class.inspect} #{@env[REQUEST_METHOD]} #{full_path_info}>"
398
487
  end
399
488
 
400
- # Does a terminal match on the input, matching only if the arguments
401
- # have fully matched the patch.
489
+ # Does a terminal match on the current path, matching only if the arguments
490
+ # have fully matched the path. If it matches, the match block is
491
+ # executed, and when the match block returns, the rack response is
492
+ # returned.
493
+ #
494
+ # r.path_info
495
+ # # => "/foo/bar"
496
+ #
497
+ # r.is 'foo' do
498
+ # # does not match, as path isn't fully matched (/bar remaining)
499
+ # end
500
+ #
501
+ # r.is 'foo/bar' do
502
+ # # matches as path is empty after matching
503
+ # end
504
+ #
505
+ # If no arguments are given, matches if the path is already fully matched.
506
+ #
507
+ # r.on 'foo/bar' do
508
+ # r.is do
509
+ # # matches as path is already empty
510
+ # end
511
+ # end
512
+ #
513
+ # Note that this matches only if the path after matching the arguments
514
+ # is empty, not if it still contains a trailing slash:
515
+ #
516
+ # r.path_info
517
+ # # => "/foo/bar/"
518
+ #
519
+ # r.is 'foo/bar' do
520
+ # # does not match, as path isn't fully matched (/ remaining)
521
+ # end
522
+ #
523
+ # r.is 'foo/bar/' do
524
+ # # matches as path is empty after matching
525
+ # end
526
+ #
527
+ # r.on 'foo/bar' do
528
+ # r.is "" do
529
+ # # matches as path is empty after matching
530
+ # end
531
+ # end
402
532
  def is(*args, &block)
403
533
  if args.empty?
404
534
  if @env[PATH_INFO] == EMPTY_STRING
@@ -410,11 +540,33 @@ class Roda
410
540
  end
411
541
  end
412
542
 
413
- # Attempts to match on all of the arguments. If all of the
414
- # arguments match, control is yielded to the block, and after
415
- # the block returns, the rack response will be returned.
416
- # If any of the arguments fails, ensures the request state is
417
- # returned to that before matches were attempted.
543
+ # Does a match on the path, matching only if the arguments
544
+ # have matched the path. Because this doesn't fully match the
545
+ # path, this is usually used to setup branches of the routing tree,
546
+ # not for final handling of the request.
547
+ #
548
+ # r.path_info
549
+ # # => "/foo/bar"
550
+ #
551
+ # r.on 'foo' do
552
+ # # matches, path is /bar after matching
553
+ # end
554
+ #
555
+ # r.on 'bar' do
556
+ # # does not match
557
+ # end
558
+ #
559
+ # Like other routing methods, If it matches, the match block is
560
+ # executed, and when the match block returns, the rack response is
561
+ # returned. However, in general you will call another routing method
562
+ # inside the match block that fully matches the path and does the
563
+ # final handling for the request:
564
+ #
565
+ # r.on 'foo' do
566
+ # r.is 'bar' do
567
+ # # handle /foo/bar request
568
+ # end
569
+ # end
418
570
  def on(*args, &block)
419
571
  if args.empty?
420
572
  always(&block)
@@ -423,26 +575,104 @@ class Roda
423
575
  end
424
576
  end
425
577
 
426
- # The response related to the current request.
578
+ # The response related to the current request. See ResponseMethods for
579
+ # instance methods for the response, but in general the most common usage
580
+ # is to override the response status and headers:
581
+ #
582
+ # response.status = 200
583
+ # response['Header-Name'] = 'Header value'
427
584
  def response
428
585
  scope.response
429
586
  end
430
587
 
431
- # Immediately redirect to the given path.
588
+ # Immediately redirect to the path using the status code. This ends
589
+ # the processing of the request:
590
+ #
591
+ # r.redirect '/page1', 301 if r['param'] == 'value1'
592
+ # r.redirect '/page2' # uses 302 status code
593
+ # response.status = 404 # not reached
594
+ #
595
+ # If you do not provide a path, by default it will redirect to the same
596
+ # path if the request is not a +GET+ request. This is designed to make
597
+ # it easy to use where a +POST+ request to a URL changes state, +GET+
598
+ # returns the current state, and you want to show the current state
599
+ # after changing:
600
+ #
601
+ # r.is "foo" do
602
+ # r.get do
603
+ # # show state
604
+ # end
605
+ #
606
+ # r.post do
607
+ # # change state
608
+ # r.redirect
609
+ # end
610
+ # end
432
611
  def redirect(path=default_redirect_path, status=302)
433
612
  response.redirect(path, status)
434
613
  throw :halt, response.finish
435
614
  end
436
615
 
437
- # If this is a GET request for the root ("/"), yield to the match block.
616
+ # Routing matches that only matches +GET+ requests where the current
617
+ # path is +/+. If it matches, the match block is executed, and when
618
+ # the match block returns, the rack response is returned.
619
+ #
620
+ # [r.request_method, r.path_info]
621
+ # # => ['GET', '/']
622
+ #
623
+ # r.root do
624
+ # # matches
625
+ # end
626
+ #
627
+ # This is usuable inside other match blocks:
628
+ #
629
+ # [r.request_method, r.path_info]
630
+ # # => ['GET', '/foo/']
631
+ #
632
+ # r.on 'foo' do
633
+ # r.root do
634
+ # # matches
635
+ # end
636
+ # end
637
+ #
638
+ # Note that this does not match non-+GET+ requests:
639
+ #
640
+ # [r.request_method, r.path_info]
641
+ # # => ['POST', '/']
642
+ #
643
+ # r.root do
644
+ # # does not match
645
+ # end
646
+ #
647
+ # Use <tt>r.post ""</tt> for +POST+ requests where the current path
648
+ # is +/+.
649
+ #
650
+ # Nor does it match empty paths:
651
+ #
652
+ # [r.request_method, r.path_info]
653
+ # # => ['GET', '/foo']
654
+ #
655
+ # r.on 'foo' do
656
+ # r.root do
657
+ # # does not match
658
+ # end
659
+ # end
660
+ #
661
+ # Use <tt>r.get true</tt> to handle +GET+ requests where the current
662
+ # path is empty.
438
663
  def root(&block)
439
664
  if @env[PATH_INFO] == SLASH && is_get?
440
665
  always(&block)
441
666
  end
442
667
  end
443
668
 
444
- # Call the given rack app with the environment and immediately return
445
- # the response as the response for this request.
669
+ # Call the given rack app with the environment and return the response
670
+ # from the rack app as the response for this request. This ends
671
+ # the processing of the request:
672
+ #
673
+ # r.run(proc{[403, {}, []]}) unless r['letmein'] == '1'
674
+ # r.run(proc{[404, {}, []]})
675
+ # response.status = 404 # not reached
446
676
  def run(app)
447
677
  throw :halt, app.call(@env)
448
678
  end
@@ -599,7 +829,7 @@ class Roda
599
829
  # Match files with the given extension. Requires that the
600
830
  # request path end with the extension.
601
831
  def match_extension(ext)
602
- consume(self.class.cached_matcher([:extension, ext]){"([^\\/]+?)\.#{ext}\\z"})
832
+ consume(self.class.cached_matcher([:extension, ext]){/([^\\\/]+)\.#{ext}/})
603
833
  end
604
834
 
605
835
  # Match by request method. This can be an array if you want
@@ -664,12 +894,16 @@ class Roda
664
894
  @length = 0
665
895
  end
666
896
 
667
- # Return the response header with the given key.
897
+ # Return the response header with the given key. Example:
898
+ #
899
+ # response['Content-Type'] # => 'text/html'
668
900
  def [](key)
669
901
  @headers[key]
670
902
  end
671
903
 
672
904
  # Set the response header with the given key to the given value.
905
+ #
906
+ # response['Content-Type'] = 'application/json'
673
907
  def []=(key, value)
674
908
  @headers[key] = value
675
909
  end
@@ -687,19 +921,29 @@ class Roda
687
921
  # Modify the headers to include a Set-Cookie value that
688
922
  # deletes the cookie. A value hash can be provided to
689
923
  # override the default one used to delete the cookie.
924
+ # Example:
925
+ #
926
+ # response.delete_cookie('foo')
927
+ # response.delete_cookie('foo', :domain=>'example.org')
690
928
  def delete_cookie(key, value = {})
691
929
  ::Rack::Utils.delete_cookie_header!(@headers, key, value)
692
930
  end
693
931
 
694
932
  # Whether the response body has been written to yet. Note
695
933
  # that writing an empty string to the response body marks
696
- # the response as not empty.
934
+ # the response as not empty. Example:
935
+ #
936
+ # response.empty? # => true
937
+ # response.write('a')
938
+ # response.empty? # => false
697
939
  def empty?
698
940
  @body.empty?
699
941
  end
700
942
 
701
943
  # Return the rack response array of status, headers, and body
702
- # for the current response.
944
+ # for the current response. Example:
945
+ #
946
+ # response.finish # => [200, {'Content-Type'=>'text/html'}, []]
703
947
  def finish
704
948
  b = @body
705
949
  s = (@status ||= b.empty? ? 404 : 200)
@@ -707,19 +951,28 @@ class Roda
707
951
  end
708
952
 
709
953
  # Set the Location header to the given path, and the status
710
- # to the given status.
954
+ # to the given status. Example:
955
+ #
956
+ # response.redirect('foo', 301)
957
+ # response.redirect('bar')
711
958
  def redirect(path, status = 302)
712
959
  @headers[LOCATION] = path
713
960
  @status = status
714
961
  end
715
962
 
716
963
  # Set the cookie with the given key in the headers.
964
+ #
965
+ # response.set_cookie('foo', 'bar')
966
+ # response.set_cookie('foo', :value=>'bar', :domain=>'example.org')
717
967
  def set_cookie(key, value)
718
968
  ::Rack::Utils.set_cookie_header!(@headers, key, value)
719
969
  end
720
970
 
721
971
  # Write to the response body. Updates Content-Length header
722
- # with the size of the string written. Returns nil.
972
+ # with the size of the string written. Returns nil. Example:
973
+ #
974
+ # response.write('foo')
975
+ # response['Content-Length'] # =>'3'
723
976
  def write(str)
724
977
  s = str.to_s
725
978