roda 3.18.0 → 3.19.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.
- 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
|