roda 3.18.0 → 3.19.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +24 -0
- data/README.rdoc +7 -9
- data/doc/conventions.rdoc +10 -10
- data/doc/release_notes/3.19.0.txt +229 -0
- data/lib/roda.rb +88 -45
- data/lib/roda/plugins/assets.rb +11 -4
- data/lib/roda/plugins/delay_build.rb +3 -30
- data/lib/roda/plugins/empty_root.rb +1 -1
- data/lib/roda/plugins/hash_routes.rb +455 -0
- data/lib/roda/plugins/match_hook.rb +69 -0
- data/lib/roda/plugins/multi_route.rb +4 -0
- data/lib/roda/plugins/multi_view.rb +4 -0
- data/lib/roda/plugins/optimized_string_matchers.rb +1 -1
- data/lib/roda/plugins/sessions.rb +63 -16
- data/lib/roda/plugins/static_routing.rb +7 -40
- data/lib/roda/version.rb +1 -1
- data/spec/define_roda_method_spec.rb +3 -0
- data/spec/freeze_spec.rb +10 -1
- data/spec/integration_spec.rb +1 -1
- data/spec/plugin/assets_spec.rb +16 -0
- data/spec/plugin/delay_build_spec.rb +2 -3
- data/spec/plugin/hash_routes_spec.rb +535 -0
- data/spec/plugin/match_hook_spec.rb +79 -0
- data/spec/plugin/middleware_spec.rb +1 -0
- data/spec/plugin/sessions_spec.rb +363 -320
- metadata +8 -2
data/lib/roda/plugins/assets.rb
CHANGED
@@ -298,7 +298,9 @@ class Roda
|
|
298
298
|
# :timestamp_paths :: Include the timestamp of assets in asset paths in non-compiled mode. Doing this can
|
299
299
|
# slow down development requests due to additional requests to get last modified times,
|
300
300
|
# put it will make sure the paths change in development when there are modifications,
|
301
|
-
# which can fix issues when using a caching proxy in non-compiled mode.
|
301
|
+
# which can fix issues when using a caching proxy in non-compiled mode. This can also
|
302
|
+
# be specified as a string to use that string to separate the timestamp from the asset.
|
303
|
+
# By default, <tt>/</tt> is used as the separator if timestamp paths are enabled.
|
302
304
|
module Assets
|
303
305
|
DEFAULTS = {
|
304
306
|
:compiled_name => 'app'.freeze,
|
@@ -375,6 +377,10 @@ class Roda
|
|
375
377
|
app.plugin :early_hints
|
376
378
|
end
|
377
379
|
|
380
|
+
if opts[:timestamp_paths] && !opts[:timestamp_paths].is_a?(String)
|
381
|
+
opts[:timestamp_paths] = '/'
|
382
|
+
end
|
383
|
+
|
378
384
|
DEFAULTS.each do |k, v|
|
379
385
|
opts[k] = v unless opts.has_key?(k)
|
380
386
|
end
|
@@ -629,9 +635,9 @@ class Roda
|
|
629
635
|
prefix = "#{dirs.join('/')}/" if o[:group_subdirs]
|
630
636
|
end
|
631
637
|
Array(asset_dir).map do |f|
|
632
|
-
if o[:timestamp_paths]
|
638
|
+
if ts = o[:timestamp_paths]
|
633
639
|
mtime = asset_last_modified(File.join(o[:"#{stype}_path"], *[prefix, f].compact))
|
634
|
-
mtime = "#{sprintf("%i%06i", mtime.to_i, mtime.usec)}
|
640
|
+
mtime = "#{sprintf("%i%06i", mtime.to_i, mtime.usec)}#{ts}"
|
635
641
|
end
|
636
642
|
"#{url_prefix}/#{o[:"#{stype}_prefix"]}#{mtime}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"
|
637
643
|
end
|
@@ -784,7 +790,8 @@ class Roda
|
|
784
790
|
/#{o[:"compiled_#{type}_prefix"]}(#{Regexp.union(assets)})/
|
785
791
|
else
|
786
792
|
assets = unnest_assets_hash(o[type])
|
787
|
-
|
793
|
+
ts = o[:timestamp_paths]
|
794
|
+
/#{o[:"#{type}_prefix"]}#{"\\d+#{ts}" if ts}(#{Regexp.union(assets.uniq)})#{o[:"#{type}_suffix"]}/
|
788
795
|
end
|
789
796
|
end
|
790
797
|
|
@@ -3,43 +3,16 @@
|
|
3
3
|
#
|
4
4
|
class Roda
|
5
5
|
module RodaPlugins
|
6
|
-
# The delay_build plugin does not build the rack app until
|
7
|
-
# Roda.app is called, and only rebuilds the rack app if Roda.build!
|
8
|
-
# is called. This differs from Roda's default behavior, which
|
9
|
-
# rebuilds the rack app every time the route block changes and
|
10
|
-
# every time middleware is added if a route block has already
|
11
|
-
# been defined.
|
12
|
-
#
|
13
|
-
# If you are loading hundreds of middleware after a
|
14
|
-
# route block has already been defined, this can fix a possible
|
15
|
-
# performance issue, turning an O(n^2) calculation into an
|
16
|
-
# O(n) calculation, where n is the number of middleware used.
|
17
6
|
module DelayBuild
|
18
7
|
module ClassMethods
|
19
|
-
#
|
20
|
-
def app
|
21
|
-
@app || build!
|
22
|
-
end
|
23
|
-
|
24
|
-
# Rebuild the application.
|
8
|
+
# No-op for backwards compatibility
|
25
9
|
def build!
|
26
|
-
@build_app = true
|
27
|
-
build_rack_app
|
28
|
-
@app
|
29
|
-
ensure
|
30
|
-
@build_app = false
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
# Do not build the rack app automatically, wait for an
|
36
|
-
# explicit call to build!.
|
37
|
-
def build_rack_app
|
38
|
-
super if @build_app
|
39
10
|
end
|
40
11
|
end
|
41
12
|
end
|
42
13
|
|
14
|
+
# RODA4: Remove plugin
|
15
|
+
# Only available for backwards compatibility, no longer needed
|
43
16
|
register_plugin(:delay_build, DelayBuild)
|
44
17
|
end
|
45
18
|
end
|
@@ -0,0 +1,455 @@
|
|
1
|
+
# frozen-string-literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
class Roda
|
5
|
+
module RodaPlugins
|
6
|
+
# The hash_routes plugin combines the O(1) dispatching speed of the static_routing plugin with
|
7
|
+
# the flexibility of the multi_route plugin. For any point in the routing tree,
|
8
|
+
# it allows you dispatch to multiple routes where the next segment or the remaining path
|
9
|
+
# is a static string.
|
10
|
+
#
|
11
|
+
# For a basic replacement of the multi_route plugin, you can replace class level
|
12
|
+
# <tt>route('segment')</tt> calls with <tt>hash_branch('segment')</tt>:
|
13
|
+
#
|
14
|
+
# class App < Roda
|
15
|
+
# plugin :hash_routes
|
16
|
+
#
|
17
|
+
# hash_branch("a") do |r|
|
18
|
+
# # /a branch
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# hash_branch("b") do |r|
|
22
|
+
# # /b branch
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# route do |r|
|
26
|
+
# r.hash_branches
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# With the above routing tree, the +r.hash_branches+ call in the main routing tree,
|
31
|
+
# will dispatch requests for the +/a+ and +/b+ branches of the tree to the appropriate
|
32
|
+
# routing blocks.
|
33
|
+
#
|
34
|
+
# In addition to supporting routing via the next segment, you can also support similar
|
35
|
+
# routing for entire remaining path using the +hash_path+ class method:
|
36
|
+
#
|
37
|
+
# class App < Roda
|
38
|
+
# plugin :hash_routes
|
39
|
+
#
|
40
|
+
# hash_path("/a") do |r|
|
41
|
+
# # /a path
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# hash_path("/a/b") do |r|
|
45
|
+
# # /a/b path
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# route do |r|
|
49
|
+
# r.hash_paths
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# With the above routing tree, the +r.hash_paths+ call will dispatch requests for the +/a+ and
|
54
|
+
# +/a/b+ request paths.
|
55
|
+
#
|
56
|
+
# You can combine the two approaches, and use +r.hash_routes+ to first try routing the
|
57
|
+
# full path, and then try routing the next segment:
|
58
|
+
#
|
59
|
+
# class App < Roda
|
60
|
+
# plugin :hash_routes
|
61
|
+
#
|
62
|
+
# hash_branch("a") do |r|
|
63
|
+
# # /a branch
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# hash_branch("b") do |r|
|
67
|
+
# # /b branch
|
68
|
+
# end
|
69
|
+
#
|
70
|
+
# hash_path("/a") do |r|
|
71
|
+
# # /a path
|
72
|
+
# end
|
73
|
+
#
|
74
|
+
# hash_path("/a/b") do |r|
|
75
|
+
# # /a/b path
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# route do |r|
|
79
|
+
# r.hash_routes
|
80
|
+
# end
|
81
|
+
# end
|
82
|
+
#
|
83
|
+
# With the above routing tree, requests for +/a+ and +/a/b+ will be routed to the appropriate
|
84
|
+
# +hash_path+ block. Other requests for the +/a+ branch, and all requests for the +/b+
|
85
|
+
# branch will be routed to the appropriate +hash_branch+ block.
|
86
|
+
#
|
87
|
+
# Both +hash_branch+ and +hash_path+ support namespaces, which allows them to be used at
|
88
|
+
# any level of the routing tree. Here is an example that uses namespaces for sub-branches:
|
89
|
+
#
|
90
|
+
# class App < Roda
|
91
|
+
# plugin :hash_routes
|
92
|
+
#
|
93
|
+
# # Only one argument used, so the namespace defaults to '', and the argument
|
94
|
+
# # specifies the route name
|
95
|
+
# hash_branch("a") do |r|
|
96
|
+
# # uses '/a' as the namespace when looking up routes,
|
97
|
+
# # as that part of the path has been routed now
|
98
|
+
# r.hash_routes
|
99
|
+
# end
|
100
|
+
#
|
101
|
+
# # Two arguments used, so first specifies the namespace and the second specifies
|
102
|
+
# # the route name
|
103
|
+
# hash_branch('', "b") do |r|
|
104
|
+
# # uses :b as the namespace when looking up routes, as that was explicitly specified
|
105
|
+
# r.hash_routes(:b)
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
# hash_path("/a", "/b") do |r|
|
109
|
+
# # /a/b path
|
110
|
+
# end
|
111
|
+
#
|
112
|
+
# hash_path("/a", "/c") do |r|
|
113
|
+
# # /a/c path
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# hash_path(:b, "/b") do |r|
|
117
|
+
# # /b/b path
|
118
|
+
# end
|
119
|
+
#
|
120
|
+
# hash_path(:b, "/c") do |r|
|
121
|
+
# # /b/c path
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# route do |r|
|
125
|
+
# # uses '' as the namespace, as no part of the path has been routed yet
|
126
|
+
# r.hash_branches
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# With the above routing tree, requests for the +/a+ and +/b+ branches will be
|
131
|
+
# dispatched to the appropriate +hash_branch+ block. Those blocks will the dispatch
|
132
|
+
# to the +hash_path+ blocks, with the +/a+ branch using the implicit namespace of
|
133
|
+
# +/a+, and the +/b+ branch using the explicit namespace of +:b+. In general, it
|
134
|
+
# is best for performance to explicitly specify the namespace when calling
|
135
|
+
# +r.hash_branches+, +r.hash_paths+, and +r.hash_routes+.
|
136
|
+
#
|
137
|
+
# Because specifying routes explicitly using the +hash_branch+ and +hash_path+
|
138
|
+
# class methods can get repetitive, the hash_routes plugin offers a DSL for DRYing
|
139
|
+
# the code up. This DSL is used by calling the +hash_routes+ class method. Below
|
140
|
+
# is a translation of the previous example to using the +hash_routes+ DSL:
|
141
|
+
#
|
142
|
+
# class App < Roda
|
143
|
+
# plugin :hash_routes
|
144
|
+
#
|
145
|
+
# # No block argument is used, DSL evaluates block using instance_exec
|
146
|
+
# hash_routes "" do
|
147
|
+
# # on method is used for routing to next segment,
|
148
|
+
# # for similarity to standard Roda
|
149
|
+
# on "a" do |r|
|
150
|
+
# r.hash_routes '/a'
|
151
|
+
# end
|
152
|
+
#
|
153
|
+
# on "b" do |r|
|
154
|
+
# r.hash_routes(:b)
|
155
|
+
# end
|
156
|
+
# end
|
157
|
+
#
|
158
|
+
# # Block argument is used, block is yielded DSL instance
|
159
|
+
# hash_routes "/a" do |hr|
|
160
|
+
# # is method is used for routing to the remaining path,
|
161
|
+
# # for similarity to standard Roda
|
162
|
+
# hr.is "b" do |r|
|
163
|
+
# # /a/b path
|
164
|
+
# end
|
165
|
+
#
|
166
|
+
# hr.is "c" do |r|
|
167
|
+
# # /a/c path
|
168
|
+
# end
|
169
|
+
# end
|
170
|
+
#
|
171
|
+
# hash_routes :b do
|
172
|
+
# is "b" do |r|
|
173
|
+
# # /b/b path
|
174
|
+
# end
|
175
|
+
#
|
176
|
+
# is "c" do |r|
|
177
|
+
# # /b/c path
|
178
|
+
# end
|
179
|
+
# end
|
180
|
+
#
|
181
|
+
# route do |r|
|
182
|
+
# # No change here, DSL only makes setup DRYer
|
183
|
+
# r.hash_branches
|
184
|
+
# end
|
185
|
+
# end
|
186
|
+
#
|
187
|
+
# The +hash_routes+ DSL also offers some additional features to handle additional
|
188
|
+
# cases. It supports verb methods, such as +get+ and +post+, which operate like
|
189
|
+
# +is+, but are only called if the verb matches (and are not yielded the request).
|
190
|
+
# It supports a +view+ method for routes that only render views, as well as a
|
191
|
+
# +views+ method for setting up routes for multiple views in a single call, which
|
192
|
+
# is a good replacement for the +multi_view+ plugin.
|
193
|
+
# +is+, +view+, and the verb methods can use a value of +true+ for the empty
|
194
|
+
# remaining path (as the empty string specifies the <tt>"/"</tt> remaining path).
|
195
|
+
# It also supports a +dispatch_from+ method, allowing you to setup dispatching to
|
196
|
+
# current group of routes from a higher-level namespace.
|
197
|
+
# The +hash_routes+ class method will return the DSL instance, so you are not
|
198
|
+
# limited to using it with a block.
|
199
|
+
#
|
200
|
+
# Here's the above example modified to use some of these features:
|
201
|
+
#
|
202
|
+
# class App < Roda
|
203
|
+
# plugin :hash_routes
|
204
|
+
#
|
205
|
+
# hash_routes "/a" do
|
206
|
+
# # Dispatch requests for the /a branch from the empty (default) routing
|
207
|
+
# # namespace to this namespace
|
208
|
+
# dispatch_from "a"
|
209
|
+
#
|
210
|
+
# # Handle GET /a path, render "a" template, returning 404 for non-GET requests
|
211
|
+
# view true, "a"
|
212
|
+
#
|
213
|
+
# # Handle /a/b path, returning 404 for non-GET requests
|
214
|
+
# get "b" do
|
215
|
+
# # GET /a/b path
|
216
|
+
# end
|
217
|
+
#
|
218
|
+
# # Handle /a/c path, returning 404 for non-POST requests
|
219
|
+
# post "c" do
|
220
|
+
# # POST /a/c path
|
221
|
+
# end
|
222
|
+
# end
|
223
|
+
#
|
224
|
+
# bhr = hash_routes(:b)
|
225
|
+
#
|
226
|
+
# # Dispatch requests for the /b branch from the empty routing to this namespace,
|
227
|
+
# # but first check routes in the :b_preauth namespace. If there is no
|
228
|
+
# # matching route in the :b_preauth namespace, call the check_authenticated!
|
229
|
+
# # method before dispatching to any of the routes in this namespace
|
230
|
+
# bhr.dispatch_from "", "b" do |r|
|
231
|
+
# r.hash_routes :b_preauth
|
232
|
+
# check_authenticated!
|
233
|
+
# end
|
234
|
+
#
|
235
|
+
# bhr.is true do |r|
|
236
|
+
# # /b path
|
237
|
+
# end
|
238
|
+
#
|
239
|
+
# bhr.is "" do |r|
|
240
|
+
# # /b/ path
|
241
|
+
# end
|
242
|
+
#
|
243
|
+
# # GET /b/d path, render 'd2' template, returning 404 for non-GET requests
|
244
|
+
# bhr.views 'd', 'd2'
|
245
|
+
#
|
246
|
+
# # GET /b/e path, render 'e' template, returning 404 for non-GET requests
|
247
|
+
# # GET /b/f path, render 'f' template, returning 404 for non-GET requests
|
248
|
+
# bhr.views %w'e f'
|
249
|
+
#
|
250
|
+
# route do |r|
|
251
|
+
# r.hash_branches
|
252
|
+
# end
|
253
|
+
# end
|
254
|
+
#
|
255
|
+
# The +view+ and +views+ method depend on the render plugin being loaded, but this
|
256
|
+
# plugin does not load the render plugin. You must load the render plugin separately
|
257
|
+
# if you want to use the +view+ and +views+ methods.
|
258
|
+
#
|
259
|
+
# Certain parts of the +hash_routes+ DSL support do not work with the
|
260
|
+
# route_block_args plugin, as doing so would reduce performance. These are:
|
261
|
+
#
|
262
|
+
# * dispatch_from
|
263
|
+
# * view
|
264
|
+
# * views
|
265
|
+
# * all verb methods (get, post, etc.)
|
266
|
+
module HashRoutes
|
267
|
+
def self.configure(app)
|
268
|
+
app.opts[:hash_branches] ||= {}
|
269
|
+
app.opts[:hash_paths] ||= {}
|
270
|
+
app.opts[:hash_routes_methods] ||= {}
|
271
|
+
end
|
272
|
+
|
273
|
+
# Internal class handling the internals of the +hash_routes+ class method blocks.
|
274
|
+
class DSL
|
275
|
+
def initialize(roda, namespace)
|
276
|
+
@roda = roda
|
277
|
+
@namespace = namespace
|
278
|
+
end
|
279
|
+
|
280
|
+
# Setup the given branch in the given namespace to dispatch to routes in this
|
281
|
+
# namespace. If a block is given, call the block with the request before
|
282
|
+
# dispatching to routes in this namespace.
|
283
|
+
def dispatch_from(namespace='', branch, &block)
|
284
|
+
ns = @namespace
|
285
|
+
if block
|
286
|
+
meth_hash = @roda.opts[:hash_routes_methods]
|
287
|
+
key = [:dispatch_from, namespace, branch].freeze
|
288
|
+
meth = meth_hash[key] = @roda.define_roda_method(meth_hash[key] || "hash_routes_dispatch_from_#{namespace}_#{branch}", 1, &block)
|
289
|
+
@roda.hash_branch(namespace, branch) do |r|
|
290
|
+
send(meth, r)
|
291
|
+
r.hash_routes(ns)
|
292
|
+
end
|
293
|
+
else
|
294
|
+
@roda.hash_branch(namespace, branch) do |r|
|
295
|
+
r.hash_routes(ns)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
# Use the segment to setup a branch in the current namespace.
|
301
|
+
def on(segment, &block)
|
302
|
+
@roda.hash_branch(@namespace, segment, &block)
|
303
|
+
end
|
304
|
+
|
305
|
+
# Use the segment to setup a path in the current namespace.
|
306
|
+
# If path is given as a string, it is prefixed with a slash.
|
307
|
+
# If path is +true+, the empty string is used as the path.
|
308
|
+
def is(path, &block)
|
309
|
+
path = path == true ? "" : "/#{path}"
|
310
|
+
@roda.hash_path(@namespace, path, &block)
|
311
|
+
end
|
312
|
+
|
313
|
+
# Use the segment to setup a path in the current namespace that
|
314
|
+
# will render the view with the given name if the GET method is
|
315
|
+
# used, and will return a 404 if another request method is used.
|
316
|
+
# If path is given as a string, it is prefixed with a slash.
|
317
|
+
# If path is +true+, the empty string is used as the path.
|
318
|
+
def view(path, template)
|
319
|
+
path = path == true ? "" : "/#{path}"
|
320
|
+
@roda.hash_path(@namespace, path) do |r|
|
321
|
+
r.get do
|
322
|
+
view(template)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
# For each template in the array of templates, setup a path in
|
328
|
+
# the current namespace for the template using the same name
|
329
|
+
# as the template.
|
330
|
+
def views(templates)
|
331
|
+
templates.each do |template|
|
332
|
+
view(template, template)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
[:get, :post, :delete, :head, :options, :link, :patch, :put, :trace, :unlink].each do |meth|
|
337
|
+
define_method(meth) do |path, &block|
|
338
|
+
verb(meth, path, &block)
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
private
|
343
|
+
|
344
|
+
# Setup a path in the current namespace for the given request method verb.
|
345
|
+
# Returns 404 for requests for the path with a different request method.
|
346
|
+
def verb(verb, path, &block)
|
347
|
+
path = path == true ? "" : "/#{path}"
|
348
|
+
meth_hash = @roda.opts[:hash_routes_methods]
|
349
|
+
key = [@namespace, path].freeze
|
350
|
+
meth = meth_hash[key] = @roda.define_roda_method(meth_hash[key] || "hash_routes_#{@namespace}_#{path}", 0, &block)
|
351
|
+
@roda.hash_path(@namespace, path) do |r|
|
352
|
+
r.send(verb) do
|
353
|
+
send(meth)
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
module ClassMethods
|
360
|
+
# Freeze the hash_routes metadata when freezing the app.
|
361
|
+
def freeze
|
362
|
+
opts[:hash_branches].freeze.each_value(&:freeze)
|
363
|
+
opts[:hash_paths].freeze.each_value(&:freeze)
|
364
|
+
opts[:hash_routes_methods].freeze
|
365
|
+
super
|
366
|
+
end
|
367
|
+
|
368
|
+
# Duplicate hash_routes metadata in subclass.
|
369
|
+
def inherited(subclass)
|
370
|
+
super
|
371
|
+
|
372
|
+
[:hash_branches, :hash_paths].each do |k|
|
373
|
+
h = subclass.opts[k]
|
374
|
+
opts[k].each do |namespace, routes|
|
375
|
+
h[namespace] = routes.dup
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
# Invoke the DSL for configuring hash routes, see DSL for methods inside the
|
381
|
+
# block. If the block accepts an argument, yield the DSL instance. If the
|
382
|
+
# block does not accept an argument, instance_exec the block in the context
|
383
|
+
# of the DSL instance.
|
384
|
+
def hash_routes(namespace='', &block)
|
385
|
+
dsl = DSL.new(self, namespace)
|
386
|
+
if block
|
387
|
+
if block.arity == 1
|
388
|
+
yield dsl
|
389
|
+
else
|
390
|
+
dsl.instance_exec(&block)
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
dsl
|
395
|
+
end
|
396
|
+
|
397
|
+
# Add branch handler for the given namespace and segment.
|
398
|
+
def hash_branch(namespace='', segment, &block)
|
399
|
+
segment = "/#{segment}"
|
400
|
+
routes = opts[:hash_branches][namespace] ||= {}
|
401
|
+
routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1, &convert_route_block(block))
|
402
|
+
end
|
403
|
+
|
404
|
+
# Add path handler for the given namespace and path. When the
|
405
|
+
# r.hash_paths method is called, checks the matching namespace
|
406
|
+
# for the full remaining path, and dispatch to that block if
|
407
|
+
# there is one.
|
408
|
+
def hash_path(namespace='', path, &block)
|
409
|
+
routes = opts[:hash_paths][namespace] ||= {}
|
410
|
+
routes[path] = define_roda_method(routes[path] || "hash_path_#{namespace}_#{path}", 1, &convert_route_block(block))
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
module RequestMethods
|
415
|
+
# Checks the matching hash_branch namespace for a branch matching the next
|
416
|
+
# segment in the remaining path, and dispatch to that block if there is one.
|
417
|
+
def hash_branches(namespace=matched_path)
|
418
|
+
rp = @remaining_path
|
419
|
+
|
420
|
+
return unless rp.getbyte(0) == 47 # "/"
|
421
|
+
|
422
|
+
if routes = roda_class.opts[:hash_branches][namespace]
|
423
|
+
if segment_end = rp.index('/', 1)
|
424
|
+
if meth = routes[rp[0, segment_end]]
|
425
|
+
@remaining_path = rp[segment_end, 100000000]
|
426
|
+
always{scope.send(meth, self)}
|
427
|
+
end
|
428
|
+
elsif meth = routes[rp]
|
429
|
+
@remaining_path = ''
|
430
|
+
always{scope.send(meth, self)}
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
# Checks the matching hash_path namespace for a branch matching the
|
436
|
+
# remaining path, and dispatch to that block if there is one.
|
437
|
+
def hash_paths(namespace=matched_path)
|
438
|
+
if (routes = roda_class.opts[:hash_paths][namespace]) && (meth = routes[@remaining_path])
|
439
|
+
@remaining_path = ''
|
440
|
+
always{scope.send(meth, self)}
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
444
|
+
# Check for matches in both the hash_path and hash_branch namespaces for
|
445
|
+
# a matching remaining path or next segment in the remaining path, respectively.
|
446
|
+
def hash_routes(namespace=matched_path)
|
447
|
+
hash_paths(namespace)
|
448
|
+
hash_branches(namespace)
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
register_plugin(:hash_routes, HashRoutes)
|
454
|
+
end
|
455
|
+
end
|