roda-cj 0.9.6 → 1.0.0

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