roda 3.17.0 → 3.18.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 +48 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +22 -4
- data/doc/release_notes/3.18.0.txt +170 -0
- data/lib/roda.rb +249 -26
- data/lib/roda/plugins/_after_hook.rb +4 -26
- data/lib/roda/plugins/_before_hook.rb +30 -2
- data/lib/roda/plugins/branch_locals.rb +2 -2
- data/lib/roda/plugins/class_level_routing.rb +9 -7
- data/lib/roda/plugins/default_headers.rb +15 -1
- data/lib/roda/plugins/default_status.rb +9 -10
- data/lib/roda/plugins/direct_call.rb +38 -0
- data/lib/roda/plugins/error_email.rb +1 -1
- data/lib/roda/plugins/error_handler.rb +37 -11
- data/lib/roda/plugins/hooks.rb +28 -30
- data/lib/roda/plugins/mail_processor.rb +16 -11
- data/lib/roda/plugins/mailer.rb +1 -1
- data/lib/roda/plugins/middleware.rb +13 -3
- data/lib/roda/plugins/multi_route.rb +3 -3
- data/lib/roda/plugins/named_templates.rb +4 -4
- data/lib/roda/plugins/path.rb +13 -8
- data/lib/roda/plugins/render.rb +2 -2
- data/lib/roda/plugins/route_block_args.rb +4 -3
- data/lib/roda/plugins/route_csrf.rb +9 -4
- data/lib/roda/plugins/sessions.rb +2 -1
- data/lib/roda/plugins/shared_vars.rb +1 -1
- data/lib/roda/plugins/static_routing.rb +7 -17
- data/lib/roda/plugins/status_handler.rb +5 -3
- data/lib/roda/plugins/view_options.rb +2 -2
- data/lib/roda/version.rb +1 -1
- data/spec/define_roda_method_spec.rb +257 -0
- data/spec/plugin/class_level_routing_spec.rb +0 -27
- data/spec/plugin/default_headers_spec.rb +7 -0
- data/spec/plugin/default_status_spec.rb +31 -1
- data/spec/plugin/direct_call_spec.rb +28 -0
- data/spec/plugin/error_handler_spec.rb +27 -0
- data/spec/plugin/hooks_spec.rb +21 -0
- data/spec/plugin/middleware_spec.rb +108 -36
- data/spec/plugin/multi_route_spec.rb +12 -0
- data/spec/plugin/route_csrf_spec.rb +27 -0
- data/spec/plugin/sessions_spec.rb +26 -1
- data/spec/plugin/static_routing_spec.rb +25 -3
- data/spec/plugin/status_handler_spec.rb +17 -0
- data/spec/route_spec.rb +39 -0
- data/spec/spec_helper.rb +2 -2
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f39ffb4b00598e7e113f06c2f6e43e968f96b2a447059de8b351c7f5cc35675a
|
|
4
|
+
data.tar.gz: eacc16913dcfd72ad822fd8b6f25e132be80123c25a6a0c29869b39a13f04b50
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8aec053c2bda39f201cb03043cfe382d5982e5e16abca381ba76220603a25d680425a8aaf4b405f4134caef4336e3586e83d2ae0f43e8c7a40279650927237d3
|
|
7
|
+
data.tar.gz: 104867afa6a526d13acd76eab634eaf9688f44131606e1c7c67f73f1464c662d16d76938217c0b878deb0d7e6217ae5586fc2764be13cdd64298b5256bd4b3b8
|
data/CHANGELOG
CHANGED
|
@@ -1,3 +1,51 @@
|
|
|
1
|
+
= 3.18.0 (2019-03-15)
|
|
2
|
+
|
|
3
|
+
* Add direct_call plugin for making Roda.call skip middleware, allowing more optimization when dispatching routes (jeremyevans)
|
|
4
|
+
|
|
5
|
+
* Improve performance of default_headers plugin by directly defining set_default_headers (jeremyevans)
|
|
6
|
+
|
|
7
|
+
* Improve performance when freezing app if certain methods have not been overridden (jeremyevans)
|
|
8
|
+
|
|
9
|
+
* Support :check_arity and :check_dynamic_arity app options for whether/how to check arity for blocks used to define methods (jeremyevans)
|
|
10
|
+
|
|
11
|
+
* Improve performance of the status_handler plugin by using methods instead of instance_exec (jeremyevans)
|
|
12
|
+
|
|
13
|
+
* Remove r.static_route method from the static_routing plugin (jeremyevans)
|
|
14
|
+
|
|
15
|
+
* Improve performance of the static_routing plugin by using methods instead of instance_exec (jeremyevans)
|
|
16
|
+
|
|
17
|
+
* Add support for the route_block_args plugin to the route_csrf plugin (jeremyevans)
|
|
18
|
+
|
|
19
|
+
* Improve performance of the route_csrf plugin by using a method instead of instance_exec (jeremyevans)
|
|
20
|
+
|
|
21
|
+
* Improve performance of the route_block_args plugin by using a method instead of instance_exec (jeremyevans)
|
|
22
|
+
|
|
23
|
+
* Improve performance of the path plugin by using methods instead of instance_exec (jeremyevans)
|
|
24
|
+
|
|
25
|
+
* Improve performance of the named_templates plugin by using methods instead of instance_exec (jeremyevans)
|
|
26
|
+
|
|
27
|
+
* Improve performance of the multi_route plugin by using methods instead of instance_exec (jeremyevans)
|
|
28
|
+
|
|
29
|
+
* Improve performance of the hooks plugin by using methods instead of instance_exec (jeremyevans)
|
|
30
|
+
|
|
31
|
+
* Improve performance of the mail_processor plugin by using methods instead of instance_exec (jeremyevans)
|
|
32
|
+
|
|
33
|
+
* Improve performance of the default_status plugin by directly defining the default_status method (jeremyevans)
|
|
34
|
+
|
|
35
|
+
* Improve performance of class_level_routing plugin using methods instead of instance_exec (jeremyevans)
|
|
36
|
+
|
|
37
|
+
* Do not have route_block_args plugin affect class_level_routes plugin (jeremyevans)
|
|
38
|
+
|
|
39
|
+
* Integrate internal after hook with error_handler plugin (jeremyevans)
|
|
40
|
+
|
|
41
|
+
* Improve performance of internal before and after hooks (jeremyevans)
|
|
42
|
+
|
|
43
|
+
* Improve performance by using method instead of instance_exec for main route block (jeremyevans)
|
|
44
|
+
|
|
45
|
+
* Add Roda.define_roda_method for defining instance methods instead of using instance_exec (jeremyevans)
|
|
46
|
+
|
|
47
|
+
* Include cookie_options when clearing the cookie (#162, #163) (eiko, jeremyevans)
|
|
48
|
+
|
|
1
49
|
= 3.17.0 (2019-02-15)
|
|
2
50
|
|
|
3
51
|
* Improve performance in the common case for RodaResponse#finish (jeremyevans)
|
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
|
@@ -103,7 +103,7 @@ The primary way routes are matched in Roda is by calling
|
|
|
103
103
|
Each of these "routing methods" takes a "match block".
|
|
104
104
|
|
|
105
105
|
Each routing method takes each of the arguments (called matchers)
|
|
106
|
-
that
|
|
106
|
+
that are given and tries to match it to the current request.
|
|
107
107
|
If the method is able to match all of the arguments, it yields to the match block;
|
|
108
108
|
otherwise, the block is skipped and execution continues.
|
|
109
109
|
|
|
@@ -592,8 +592,9 @@ with your application code. Some of the things Roda does:
|
|
|
592
592
|
are <tt>@_request</tt> and <tt>@_response</tt>. All instance variables in the
|
|
593
593
|
scope of the +route+ block used by plugins that ship with Roda are prefixed
|
|
594
594
|
with an underscore.
|
|
595
|
-
- The
|
|
596
|
-
+
|
|
595
|
+
- The main methods defined, beyond the default methods for +Object+, are
|
|
596
|
+
+env+, +opts+, +request+, +response+, and +session+. +call+ and +_call+ are also
|
|
597
|
+
defined, but are deprecated. All other methods defined are prefixed with +_roda_+
|
|
597
598
|
- Constants inside the Roda namespace are all prefixed with +Roda+
|
|
598
599
|
(e.g., <tt>Roda::RodaRequest</tt>).
|
|
599
600
|
|
|
@@ -698,6 +699,23 @@ The following options are respected by the default library or multiple plugins:
|
|
|
698
699
|
|
|
699
700
|
:add_script_name :: Prepend the SCRIPT_NAME for the request to paths. This is
|
|
700
701
|
useful if you mount the app as a path under another app.
|
|
702
|
+
:check_arity :: Whether arity for blocks passed to Roda should be checked
|
|
703
|
+
to determine if they can be used directly to define methods
|
|
704
|
+
or need to be wrapped. By default, for backwards compatibility,
|
|
705
|
+
this is true, so Roda will check blocks and handle cases where
|
|
706
|
+
the arity of the block does not match the expected arity. This
|
|
707
|
+
can be set to +:warn+ to issue warnings whenever Roda detects an
|
|
708
|
+
arity mismatch. If set to +false+, Roda does not check the arity
|
|
709
|
+
of blocks, which can result in failures at runtime if the arity
|
|
710
|
+
of the block does not match what Roda expects. Note that Roda
|
|
711
|
+
does not check the arity for lambda blocks, as those are strict
|
|
712
|
+
by default.
|
|
713
|
+
:check_dynamic_arity :: Similar to :check_arity, but used for checking blocks
|
|
714
|
+
where the number of arguments Roda will call the blocks
|
|
715
|
+
with is not possible to determine when defining the
|
|
716
|
+
method. By default, Roda checks arity for such methods,
|
|
717
|
+
but doing so actually slows the method down even if the
|
|
718
|
+
number of arguments matches the expected number of arguments.
|
|
701
719
|
:freeze_middleware :: Whether to freeze all middleware when building the rack app.
|
|
702
720
|
:json_parser :: A callable for parsing JSON (+JSON.parse+ in general used by
|
|
703
721
|
default).
|
|
@@ -831,7 +849,7 @@ but before handling other requests:
|
|
|
831
849
|
|
|
832
850
|
The easiest way to prevent XSS with Roda is to use a template library
|
|
833
851
|
that automatically escapes output by default.
|
|
834
|
-
The +:escape+ option to the +render+ plugin sets the
|
|
852
|
+
The +:escape+ option to the +render+ plugin sets the ERB template processor
|
|
835
853
|
to escape by default, so that in your templates:
|
|
836
854
|
|
|
837
855
|
<%= '<>' %> # outputs <>
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
= New Features
|
|
2
|
+
|
|
3
|
+
* A direct_call plugin has been added. This plugin makes Roda.call
|
|
4
|
+
call the app directly, skipping any middleware. This plugin
|
|
5
|
+
can be used for performance reasons, as the class itself can be
|
|
6
|
+
used as the base rack app, instead of using a lambda as the base
|
|
7
|
+
rack app. Roda.app.call will still call all middleware when
|
|
8
|
+
using this plugin.
|
|
9
|
+
|
|
10
|
+
= Other Improvements
|
|
11
|
+
|
|
12
|
+
* Blocks that are given during application configuration, and
|
|
13
|
+
previously executed with instance_exec, instead now define methods,
|
|
14
|
+
and Roda now calls these methods. This is a much faster approach.
|
|
15
|
+
This new approach, combined with the direct_call plugin and the
|
|
16
|
+
Roda.freeze optimizations, can be over 80% faster for trivial
|
|
17
|
+
applications, with measureable improvements in most applications.
|
|
18
|
+
|
|
19
|
+
As methods are strict in regards to arity and instance_exec is
|
|
20
|
+
not, Roda now checks all such blocks for arity mismatches, and
|
|
21
|
+
attempts to compensate for arity mismatches. In case of an arity
|
|
22
|
+
mismatch, Roda will define a method that will call instance_exec,
|
|
23
|
+
in which case there will not be a performance improvement.
|
|
24
|
+
|
|
25
|
+
For some methods, Roda may not know the expected arity until
|
|
26
|
+
runtime. In that case, Roda will check the arity at runtime and
|
|
27
|
+
try to call the method with the arity that it supports if there is
|
|
28
|
+
an arity mismatch.
|
|
29
|
+
|
|
30
|
+
You can control the checking of arity via two options:
|
|
31
|
+
|
|
32
|
+
:check_arity :: Set to false to turn off all arity checking. Set to
|
|
33
|
+
:warn to issue a warning when defining the method if
|
|
34
|
+
there is an arity mismatch (for methods where the
|
|
35
|
+
expected arity is known in advance).
|
|
36
|
+
:check_dynamic_arity :: Set to false to turn off arity checking for
|
|
37
|
+
methods defined where the arity is not known
|
|
38
|
+
at compile time. Set to :warn to issue a
|
|
39
|
+
warning at runtime every time the method is
|
|
40
|
+
called and there is an arity mismatch (for
|
|
41
|
+
methods where the expected arity is not
|
|
42
|
+
known in advance). Note that checking the
|
|
43
|
+
arity at runtime has a performance cost,
|
|
44
|
+
so for maximum performance this should be
|
|
45
|
+
set to false.
|
|
46
|
+
|
|
47
|
+
Note that this arity checking is only done to keep backwards
|
|
48
|
+
compatibility. Since lambdas already used strict arity, no arity
|
|
49
|
+
checking is done if the block is a lambda and not a regular proc.
|
|
50
|
+
|
|
51
|
+
Roda has a new dispatch API that works with these defined methods.
|
|
52
|
+
The new dispatch API uses the following methods:
|
|
53
|
+
|
|
54
|
+
* _roda_handle_main_route: Entry point for normal request dispatch.
|
|
55
|
+
* _roda_handle_route: Yields to the routing block, catching any
|
|
56
|
+
halts inside the block, treating the block as a routing block.
|
|
57
|
+
* _roda_main_route: Roda.route defines this method using the
|
|
58
|
+
block provided, it accepts the request as an argument.
|
|
59
|
+
* _roda_run_main_route: Calls _roda_main_route with the request,
|
|
60
|
+
allowing for plugins to execute code around the main routing,
|
|
61
|
+
while still being able to throw :halt to return a response.
|
|
62
|
+
|
|
63
|
+
All instance methods defined by Roda use the _roda_ prefix.
|
|
64
|
+
|
|
65
|
+
* When deleting the session cookie in the sessions plugin, the
|
|
66
|
+
Set-Cookie response header now uses the same path and domain
|
|
67
|
+
that was originally used to set the cookie. This can fix cases
|
|
68
|
+
where the cookie was not being cleared as expected.
|
|
69
|
+
|
|
70
|
+
* Freezing a Roda app now can add performance improvements in
|
|
71
|
+
addition to reliability improvements. When freezing the class,
|
|
72
|
+
if certain methods in the class have not been overridden, Roda
|
|
73
|
+
now defines aliases or more optimized methods to improve
|
|
74
|
+
performance.
|
|
75
|
+
|
|
76
|
+
* Roda now warns if the Roda#call method is overridden in a module,
|
|
77
|
+
without the module also overridding _roda_handle_main_route or
|
|
78
|
+
_roda_run_main_route. This indicates that the module needs
|
|
79
|
+
to be updated to use Roda's new dispatch API. Roda will continue
|
|
80
|
+
to work in this case, but it will be slower than the Roda's now
|
|
81
|
+
default behavior, as it will force usage of the old dispatch API.
|
|
82
|
+
This check will be removed in Roda 4, which will remove support
|
|
83
|
+
for Roda#call (and Roda#_call).
|
|
84
|
+
|
|
85
|
+
* When there is only a single internal before or after hook defined,
|
|
86
|
+
the hook is now faster by using a method alias.
|
|
87
|
+
|
|
88
|
+
* The route_csrf plugin block or :csrf_failure option proc now
|
|
89
|
+
integrates with the route_block_args plugin.
|
|
90
|
+
|
|
91
|
+
* The default_status plugin is now faster by defining the
|
|
92
|
+
default_status method directly.
|
|
93
|
+
|
|
94
|
+
* The default_headers plugin is now faster by defining an optimized
|
|
95
|
+
set_default_headers method directly.
|
|
96
|
+
|
|
97
|
+
* The hooks plugin is now faster by defining methods for each
|
|
98
|
+
hook block, with a main hook method that dispatches to each
|
|
99
|
+
of the hook block methods. If only a single hook block is
|
|
100
|
+
used, the main hook method is an alias to the hook block
|
|
101
|
+
method to avoid an extra method call.
|
|
102
|
+
|
|
103
|
+
* The following plugins now use define_method instead of
|
|
104
|
+
instance_exec for better performance:
|
|
105
|
+
|
|
106
|
+
* defaults_setter
|
|
107
|
+
* mail_processor
|
|
108
|
+
* multi_route
|
|
109
|
+
* named_templates
|
|
110
|
+
* path
|
|
111
|
+
* route_block_args
|
|
112
|
+
* route_csrf
|
|
113
|
+
* static_routing
|
|
114
|
+
* status_handler
|
|
115
|
+
|
|
116
|
+
* The internal after hook implementation has now been merged into
|
|
117
|
+
the error_handler plugin. This is faster in cases where the
|
|
118
|
+
error_handler plugin is used, and slower in cases where the
|
|
119
|
+
internal after hook plugin was used without the error_handler
|
|
120
|
+
plugin.
|
|
121
|
+
|
|
122
|
+
* The route_block_args plugin now handles cases where
|
|
123
|
+
Roda.convert_route_block has already been overridden.
|
|
124
|
+
|
|
125
|
+
* Performance of routing methods that can yield captures has been
|
|
126
|
+
improved.
|
|
127
|
+
|
|
128
|
+
* Hash#merge is now used in preference to Hash[].merge! in cases
|
|
129
|
+
where the receiver of Hash#merge would not be provided by the
|
|
130
|
+
user. This is because Hash#merge is faster than Hash[].merge!
|
|
131
|
+
in recent ruby versions. If the receiver of #merge is provided
|
|
132
|
+
by the user, then Hash[].merge! is still used to ensure that the
|
|
133
|
+
resulting value is plain hash.
|
|
134
|
+
|
|
135
|
+
* The static_routing plugin no longer removes existing static
|
|
136
|
+
routes if loaded more than once.
|
|
137
|
+
|
|
138
|
+
* Roda now warns when calling Roda.route without a block.
|
|
139
|
+
|
|
140
|
+
= Backwards Compatibility
|
|
141
|
+
|
|
142
|
+
* The route_block_args plugin no longer affects the
|
|
143
|
+
class_level_routing plugin. Support for this was added in Roda
|
|
144
|
+
3.17.0 when the route_block_args plugin was added, but this was a
|
|
145
|
+
mistake as class_level_routing blocks should be called with the
|
|
146
|
+
captures for their matchers, not with the route block args.
|
|
147
|
+
|
|
148
|
+
* Some of the internal state was changed in the following plugins:
|
|
149
|
+
|
|
150
|
+
* class_level_routing
|
|
151
|
+
* mail_processor
|
|
152
|
+
* multi_route
|
|
153
|
+
* named_templates
|
|
154
|
+
* static_routing
|
|
155
|
+
* status_handler
|
|
156
|
+
|
|
157
|
+
This only affects you if you were accessing the internal state
|
|
158
|
+
via the opts hash.
|
|
159
|
+
|
|
160
|
+
* The static_routing plugin no longer defines the r.static_route
|
|
161
|
+
method.
|
|
162
|
+
|
|
163
|
+
* The mailer plugin was switched to use the new dispatch API, and
|
|
164
|
+
will no longer handle cases where the old dispatch API (Roda#call)
|
|
165
|
+
was overrridden.
|
|
166
|
+
|
|
167
|
+
* The static_route method in the static_routing plugin must
|
|
168
|
+
now be called with a block. Previously, that would not
|
|
169
|
+
cause a failure until runtime, where it would fail when
|
|
170
|
+
you tried to execute the route.
|
data/lib/roda.rb
CHANGED
|
@@ -145,6 +145,106 @@ class Roda
|
|
|
145
145
|
build_rack_app
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
# Define an instance method using the block with the provided name and
|
|
149
|
+
# expected arity. If the name is given as a Symbol, it is used directly.
|
|
150
|
+
# If the name is given as a String, a unique name will be generated using
|
|
151
|
+
# that string. The expected arity should be either 0 (no arguments),
|
|
152
|
+
# 1 (single argument), or :any (any number of arguments).
|
|
153
|
+
#
|
|
154
|
+
# If the :check_arity app option is not set to false, Roda will check that
|
|
155
|
+
# the arity of the block matches the expected arity, and compensate for
|
|
156
|
+
# cases where it does not. If it is set to :warn, Roda will warn in the
|
|
157
|
+
# cases where the arity does not match what is expected.
|
|
158
|
+
#
|
|
159
|
+
# If the expected arity is :any, Roda must perform a dynamic arity check
|
|
160
|
+
# when the method is called, which can hurt performance even in the case
|
|
161
|
+
# where the arity matches. The :check_dynamic_arity app option can be
|
|
162
|
+
# set to false to turn off the dynamic arity checks. The
|
|
163
|
+
# :check_dynamic_arity app option can be to :warn to warn if Roda needs
|
|
164
|
+
# to adjust arity dynamically.
|
|
165
|
+
#
|
|
166
|
+
# Roda only checks arity for regular blocks, not lambda blocks, as the
|
|
167
|
+
# fixes Roda uses for regular blocks would not work for lambda blocks.
|
|
168
|
+
#
|
|
169
|
+
# Roda does not support blocks with required keyword arguments if the
|
|
170
|
+
# expected arity is 0 or 1.
|
|
171
|
+
def define_roda_method(meth, expected_arity, &block)
|
|
172
|
+
if meth.is_a?(String)
|
|
173
|
+
meth = roda_method_name(meth)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
|
|
177
|
+
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
|
|
178
|
+
|
|
179
|
+
if keyword == :required && (expected_arity == 0 || expected_arity == 1)
|
|
180
|
+
raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
case expected_arity
|
|
184
|
+
when 0
|
|
185
|
+
unless required_args == 0
|
|
186
|
+
if check_arity == :warn
|
|
187
|
+
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}"
|
|
188
|
+
end
|
|
189
|
+
b = block
|
|
190
|
+
block = lambda{instance_exec(&b)} # Fallback
|
|
191
|
+
end
|
|
192
|
+
when 1
|
|
193
|
+
if required_args == 0 && optional_args == 0 && !rest
|
|
194
|
+
if check_arity == :warn
|
|
195
|
+
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}"
|
|
196
|
+
end
|
|
197
|
+
b = block
|
|
198
|
+
block = lambda{|_| instance_exec(&b)} # Fallback
|
|
199
|
+
end
|
|
200
|
+
when :any
|
|
201
|
+
if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity)
|
|
202
|
+
if keyword
|
|
203
|
+
# Complexity of handling keyword arguments using define_method is too high,
|
|
204
|
+
# Fallback to instance_exec in this case.
|
|
205
|
+
b = block
|
|
206
|
+
block = lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
|
|
207
|
+
else
|
|
208
|
+
arity_meth = meth
|
|
209
|
+
meth = :"#{meth}_arity"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
else
|
|
213
|
+
raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}"
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
define_method(meth, &block)
|
|
218
|
+
private meth
|
|
219
|
+
|
|
220
|
+
if arity_meth
|
|
221
|
+
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth))
|
|
222
|
+
max_args = required_args + optional_args
|
|
223
|
+
define_method(arity_meth) do |*a|
|
|
224
|
+
arity = a.length
|
|
225
|
+
if arity > required_args
|
|
226
|
+
if arity > max_args && !rest
|
|
227
|
+
if check_dynamic_arity == :warn
|
|
228
|
+
RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. At most #{max_args} arguments accepted, but #{arity} arguments given for #{block.inspect}"
|
|
229
|
+
end
|
|
230
|
+
a = a.slice(0, max_args)
|
|
231
|
+
end
|
|
232
|
+
elsif arity < required_args
|
|
233
|
+
if check_dynamic_arity == :warn
|
|
234
|
+
RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}"
|
|
235
|
+
end
|
|
236
|
+
a.concat([nil] * (required_args - arity))
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
send(meth, *a)
|
|
240
|
+
end
|
|
241
|
+
private arity_meth
|
|
242
|
+
arity_meth
|
|
243
|
+
else
|
|
244
|
+
meth
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
|
|
148
248
|
# Expand the given path, using the root argument as the base directory.
|
|
149
249
|
def expand_path(path, root=opts[:root])
|
|
150
250
|
::File.expand_path(path, root)
|
|
@@ -160,6 +260,24 @@ class Roda
|
|
|
160
260
|
def freeze
|
|
161
261
|
@opts.freeze
|
|
162
262
|
@middleware.freeze
|
|
263
|
+
|
|
264
|
+
unless opts[:subclassed]
|
|
265
|
+
# If the _roda_run_main_route instance method has not been overridden,
|
|
266
|
+
# make it an alias to _roda_main_route for performance
|
|
267
|
+
if instance_method(:_roda_run_main_route).owner == InstanceMethods
|
|
268
|
+
class_eval("alias _roda_run_main_route _roda_main_route")
|
|
269
|
+
end
|
|
270
|
+
self::RodaResponse.class_eval do
|
|
271
|
+
if instance_method(:set_default_headers).owner == ResponseMethods &&
|
|
272
|
+
instance_method(:default_headers).owner == ResponseMethods
|
|
273
|
+
|
|
274
|
+
def set_default_headers
|
|
275
|
+
@headers['Content-Type'] ||= 'text/html'
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
|
|
163
281
|
super
|
|
164
282
|
end
|
|
165
283
|
|
|
@@ -176,10 +294,16 @@ class Roda
|
|
|
176
294
|
# and setup the request and response subclasses.
|
|
177
295
|
def inherited(subclass)
|
|
178
296
|
raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
|
|
297
|
+
|
|
298
|
+
# Mark current class as having been subclassed, as some optimizations
|
|
299
|
+
# depend on the class not being subclassed
|
|
300
|
+
opts[:subclassed] = true
|
|
301
|
+
|
|
179
302
|
super
|
|
180
303
|
subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
|
|
181
304
|
subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
|
|
182
305
|
subclass.instance_variable_set(:@opts, opts.dup)
|
|
306
|
+
subclass.opts.delete(:subclassed)
|
|
183
307
|
subclass.opts.to_a.each do |k,v|
|
|
184
308
|
if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
|
|
185
309
|
subclass.opts[k] = v.dup
|
|
@@ -233,9 +357,15 @@ class Roda
|
|
|
233
357
|
# This should only be called once per class, and if called multiple
|
|
234
358
|
# times will overwrite the previous routing.
|
|
235
359
|
def route(&block)
|
|
360
|
+
unless block
|
|
361
|
+
RodaPlugins.warn "no block passed to Roda.route"
|
|
362
|
+
return
|
|
363
|
+
end
|
|
364
|
+
|
|
236
365
|
@raw_route_block = block
|
|
237
366
|
@route_block = block = convert_route_block(block)
|
|
238
|
-
@rack_app_route_block = rack_app_route_block(block)
|
|
367
|
+
@rack_app_route_block = block = rack_app_route_block(block)
|
|
368
|
+
public define_roda_method(:_roda_main_route, 1, &block)
|
|
239
369
|
build_rack_app
|
|
240
370
|
end
|
|
241
371
|
|
|
@@ -250,10 +380,68 @@ class Roda
|
|
|
250
380
|
|
|
251
381
|
private
|
|
252
382
|
|
|
383
|
+
# Return the number of required argument, optional arguments,
|
|
384
|
+
# whether the callable accepts any additional arguments,
|
|
385
|
+
# and whether the callable accepts keyword arguments (true, false
|
|
386
|
+
# or :required).
|
|
387
|
+
def _define_roda_method_arg_numbers(callable)
|
|
388
|
+
optional_args = 0
|
|
389
|
+
rest = false
|
|
390
|
+
keyword = false
|
|
391
|
+
callable.parameters.map(&:first).each do |arg_type, _|
|
|
392
|
+
case arg_type
|
|
393
|
+
when :opt
|
|
394
|
+
optional_args += 1
|
|
395
|
+
when :rest
|
|
396
|
+
rest = true
|
|
397
|
+
when :keyreq
|
|
398
|
+
keyword = :required
|
|
399
|
+
when :key, :keyrest
|
|
400
|
+
keyword ||= true
|
|
401
|
+
end
|
|
402
|
+
end
|
|
403
|
+
arity = callable.arity
|
|
404
|
+
if arity < 0
|
|
405
|
+
arity = arity.abs - 1
|
|
406
|
+
end
|
|
407
|
+
required_args = arity
|
|
408
|
+
arity -= 1 if keyword == :required
|
|
409
|
+
|
|
410
|
+
if callable.is_a?(Proc) && !callable.lambda?
|
|
411
|
+
optional_args -= arity
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
[required_args, optional_args, rest, keyword]
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# The base rack app to use, before middleware is added.
|
|
418
|
+
def base_rack_app_callable(new_api=true)
|
|
419
|
+
if new_api
|
|
420
|
+
lambda{|env| new(env)._roda_handle_main_route}
|
|
421
|
+
else
|
|
422
|
+
block = @rack_app_route_block
|
|
423
|
+
lambda{|env| new(env).call(&block)}
|
|
424
|
+
end
|
|
425
|
+
end
|
|
426
|
+
|
|
253
427
|
# Build the rack app to use
|
|
254
428
|
def build_rack_app
|
|
255
|
-
if
|
|
256
|
-
|
|
429
|
+
if @rack_app_route_block
|
|
430
|
+
# RODA4: Assume optimize is true
|
|
431
|
+
optimize = ancestors.each do |mod|
|
|
432
|
+
break true if mod == InstanceMethods
|
|
433
|
+
meths = mod.instance_methods(false)
|
|
434
|
+
if meths.include?(:call) && !(meths.include?(:_roda_handle_main_route) || meths.include?(:_roda_run_main_route))
|
|
435
|
+
RodaPlugins.warn <<WARNING
|
|
436
|
+
Falling back to using #call for dispatching for #{self}, due to #call override in #{mod}.
|
|
437
|
+
#{mod} should be fixed to adjust to Roda's new dispatch API, and override _roda_handle_main_route or _roda_run_main_route
|
|
438
|
+
WARNING
|
|
439
|
+
break false
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
app = base_rack_app_callable(optimize)
|
|
444
|
+
|
|
257
445
|
@middleware.reverse_each do |args, bl|
|
|
258
446
|
mid, *args = args
|
|
259
447
|
app = mid.new(app, *args, &bl)
|
|
@@ -274,13 +462,15 @@ class Roda
|
|
|
274
462
|
# in order, if any _roda_before_* methods are defined. Also, rebuild
|
|
275
463
|
# the route block if a _roda_before method is defined.
|
|
276
464
|
def def_roda_before
|
|
277
|
-
meths = private_instance_methods.grep(/\A_roda_before_\d\d/).sort
|
|
465
|
+
meths = private_instance_methods.grep(/\A_roda_before_\d\d/).sort
|
|
278
466
|
unless meths.empty?
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
467
|
+
plugin :_before_hook unless private_method_defined?(:_roda_before)
|
|
468
|
+
if meths.length == 1
|
|
469
|
+
class_eval("alias _roda_before #{meths.first}", __FILE__, __LINE__)
|
|
470
|
+
else
|
|
471
|
+
class_eval("def _roda_before; #{meths.join(';')} end", __FILE__, __LINE__)
|
|
283
472
|
end
|
|
473
|
+
private :_roda_before
|
|
284
474
|
end
|
|
285
475
|
end
|
|
286
476
|
|
|
@@ -288,10 +478,14 @@ class Roda
|
|
|
288
478
|
# in order, if any _roda_after_* methods are defined. Also, use
|
|
289
479
|
# the internal after hook plugin if the _roda_after method is defined.
|
|
290
480
|
def def_roda_after
|
|
291
|
-
meths = private_instance_methods.grep(/\A_roda_after_\d\d/).sort
|
|
481
|
+
meths = private_instance_methods.grep(/\A_roda_after_\d\d/).sort
|
|
292
482
|
unless meths.empty?
|
|
293
|
-
plugin :
|
|
294
|
-
|
|
483
|
+
plugin :error_handler unless private_method_defined?(:_roda_after)
|
|
484
|
+
if meths.length == 1
|
|
485
|
+
class_eval("alias _roda_after #{meths.first}", __FILE__, __LINE__)
|
|
486
|
+
else
|
|
487
|
+
class_eval("def _roda_after(res); #{meths.map{|s| "#{s}(res)"}.join(';')} end", __FILE__, __LINE__)
|
|
488
|
+
end
|
|
295
489
|
private :_roda_after
|
|
296
490
|
end
|
|
297
491
|
end
|
|
@@ -302,14 +496,14 @@ class Roda
|
|
|
302
496
|
# if any before hooks are defined.
|
|
303
497
|
# Can be modified by plugins.
|
|
304
498
|
def rack_app_route_block(block)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
499
|
+
block
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
method_num = 0
|
|
503
|
+
method_num_mutex = Mutex.new
|
|
504
|
+
# Return a unique method name symbol for the given suffix.
|
|
505
|
+
define_method(:roda_method_name) do |suffix|
|
|
506
|
+
:"_roda_#{suffix}_#{method_num_mutex.synchronize{method_num += 1}}"
|
|
313
507
|
end
|
|
314
508
|
end
|
|
315
509
|
|
|
@@ -328,20 +522,49 @@ class Roda
|
|
|
328
522
|
@_response = klass::RodaResponse.new
|
|
329
523
|
end
|
|
330
524
|
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
|
|
334
|
-
|
|
525
|
+
# Handle dispatching to the main route, catching :halt and handling
|
|
526
|
+
# the result of the block.
|
|
527
|
+
def _roda_handle_main_route
|
|
528
|
+
catch(:halt) do
|
|
529
|
+
r = @_request
|
|
530
|
+
r.block_result(_roda_run_main_route(r))
|
|
531
|
+
@_response.finish
|
|
532
|
+
end
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# Treat the given block as a routing block, catching :halt if
|
|
536
|
+
# thrown by the block.
|
|
537
|
+
def _roda_handle_route
|
|
538
|
+
catch(:halt) do
|
|
539
|
+
@_request.block_result(yield)
|
|
540
|
+
@_response.finish
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
# Default implementation of the main route, usually overridden
|
|
545
|
+
# by Roda.route.
|
|
546
|
+
def _roda_main_route(_)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# Run the main route block with the request. Designed for
|
|
550
|
+
# extension by plugins
|
|
551
|
+
def _roda_run_main_route(r)
|
|
552
|
+
_roda_main_route(r)
|
|
553
|
+
end
|
|
554
|
+
|
|
555
|
+
# Deprecated method for the previous main route dispatch API.
|
|
335
556
|
def call(&block)
|
|
557
|
+
# RODA4: Remove
|
|
336
558
|
catch(:halt) do
|
|
337
559
|
r = @_request
|
|
338
|
-
r.block_result(instance_exec(r, &block))
|
|
560
|
+
r.block_result(instance_exec(r, &block)) # Fallback
|
|
339
561
|
@_response.finish
|
|
340
562
|
end
|
|
341
563
|
end
|
|
342
564
|
|
|
343
|
-
#
|
|
565
|
+
# Deprecated private alias for internal use
|
|
344
566
|
alias _call call
|
|
567
|
+
# RODA4: Remove
|
|
345
568
|
private :_call
|
|
346
569
|
|
|
347
570
|
# The environment hash for the current request. Example:
|
|
@@ -904,7 +1127,7 @@ class Roda
|
|
|
904
1127
|
path = @remaining_path
|
|
905
1128
|
# For every block, we make sure to reset captures so that
|
|
906
1129
|
# nesting matchers won't mess with each other's captures.
|
|
907
|
-
@captures.clear
|
|
1130
|
+
captures = @captures.clear
|
|
908
1131
|
|
|
909
1132
|
if match_all(args)
|
|
910
1133
|
block_result(yield(*captures))
|