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/CHANGELOG +16 -0
- data/README.rdoc +211 -103
- data/Rakefile +1 -1
- data/doc/release_notes/1.0.0.txt +329 -0
- data/lib/roda.rb +295 -42
- data/lib/roda/plugins/all_verbs.rb +1 -1
- data/lib/roda/plugins/assets.rb +277 -0
- data/lib/roda/plugins/backtracking_array.rb +1 -1
- data/lib/roda/plugins/error_email.rb +110 -0
- data/lib/roda/plugins/multi_route.rb +14 -3
- data/lib/roda/plugins/not_allowed.rb +10 -3
- data/lib/roda/plugins/path.rb +38 -0
- data/lib/roda/plugins/symbol_matchers.rb +1 -1
- data/lib/roda/plugins/view_subdirs.rb +7 -1
- data/lib/roda/version.rb +1 -1
- data/spec/integration_spec.rb +95 -3
- data/spec/plugin/_erubis_escaping_spec.rb +1 -0
- data/spec/plugin/assets_spec.rb +86 -0
- data/spec/plugin/error_email_spec.rb +68 -0
- data/spec/plugin/multi_route_spec.rb +22 -0
- data/spec/plugin/not_allowed_spec.rb +13 -0
- data/spec/plugin/path_spec.rb +29 -0
- metadata +104 -66
- checksums.yaml +0 -7
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>' %> # <escaped>
|
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.
|
data/lib/roda.rb
CHANGED
@@ -51,9 +51,10 @@ class Roda
|
|
51
51
|
@roda_class = ::Roda
|
52
52
|
end
|
53
53
|
|
54
|
-
@
|
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,
|
112
|
-
#
|
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
|
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
|
-
@
|
187
|
-
|
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
|
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
|
-
#
|
379
|
-
# Rack::Request get? method, but can be
|
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
|
401
|
-
# have fully matched the
|
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
|
-
#
|
414
|
-
#
|
415
|
-
#
|
416
|
-
#
|
417
|
-
#
|
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
|
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
|
-
#
|
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
|
445
|
-
# the
|
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]){
|
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
|
|