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/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
|
|