actionpack 4.0.1 → 4.2.11.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +402 -1173
- data/MIT-LICENSE +1 -1
- data/README.rdoc +7 -7
- data/lib/abstract_controller/base.rb +39 -7
- data/lib/abstract_controller/callbacks.rb +32 -53
- data/lib/abstract_controller/collector.rb +11 -1
- data/lib/abstract_controller/helpers.rb +26 -16
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/rendering.rb +57 -127
- data/lib/abstract_controller/url_for.rb +1 -1
- data/lib/abstract_controller.rb +1 -2
- data/lib/action_controller/base.rb +19 -10
- data/lib/action_controller/caching/fragments.rb +7 -1
- data/lib/action_controller/caching.rb +2 -12
- data/lib/action_controller/log_subscriber.rb +29 -20
- data/lib/action_controller/metal/conditional_get.rb +37 -12
- data/lib/action_controller/metal/data_streaming.rb +1 -1
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/exceptions.rb +1 -1
- data/lib/action_controller/metal/flash.rb +17 -0
- data/lib/action_controller/metal/force_ssl.rb +2 -2
- data/lib/action_controller/metal/head.rb +8 -6
- data/lib/action_controller/metal/helpers.rb +6 -2
- data/lib/action_controller/metal/http_authentication.rb +45 -23
- data/lib/action_controller/metal/instrumentation.rb +9 -6
- data/lib/action_controller/metal/live.rb +173 -20
- data/lib/action_controller/metal/mime_responds.rb +127 -232
- data/lib/action_controller/metal/params_wrapper.rb +16 -9
- data/lib/action_controller/metal/rack_delegation.rb +1 -1
- data/lib/action_controller/metal/redirecting.rb +34 -26
- data/lib/action_controller/metal/renderers.rb +39 -12
- data/lib/action_controller/metal/rendering.rb +41 -14
- data/lib/action_controller/metal/request_forgery_protection.rb +147 -19
- data/lib/action_controller/metal/streaming.rb +19 -21
- data/lib/action_controller/metal/strong_parameters.rb +166 -22
- data/lib/action_controller/metal/testing.rb +0 -1
- data/lib/action_controller/metal/url_for.rb +11 -12
- data/lib/action_controller/metal.rb +14 -8
- data/lib/action_controller/model_naming.rb +1 -1
- data/lib/action_controller/railtie.rb +5 -1
- data/lib/action_controller/test_case.rb +160 -94
- data/lib/action_controller.rb +2 -18
- data/lib/action_dispatch/http/cache.rb +5 -4
- data/lib/action_dispatch/http/filter_parameters.rb +2 -2
- data/lib/action_dispatch/http/filter_redirect.rb +5 -4
- data/lib/action_dispatch/http/headers.rb +46 -10
- data/lib/action_dispatch/http/mime_negotiation.rb +31 -4
- data/lib/action_dispatch/http/mime_type.rb +25 -26
- data/lib/action_dispatch/http/mime_types.rb +1 -0
- data/lib/action_dispatch/http/parameter_filter.rb +1 -1
- data/lib/action_dispatch/http/parameters.rb +25 -41
- data/lib/action_dispatch/http/request.rb +49 -32
- data/lib/action_dispatch/http/response.rb +127 -25
- data/lib/action_dispatch/http/upload.rb +9 -21
- data/lib/action_dispatch/http/url.rb +97 -70
- data/lib/action_dispatch/journey/formatter.rb +35 -19
- data/lib/action_dispatch/journey/gtg/builder.rb +3 -3
- data/lib/action_dispatch/journey/gtg/simulator.rb +10 -7
- data/lib/action_dispatch/journey/gtg/transition_table.rb +23 -33
- data/lib/action_dispatch/journey/nfa/dot.rb +2 -2
- data/lib/action_dispatch/journey/nfa/simulator.rb +1 -1
- data/lib/action_dispatch/journey/nfa/transition_table.rb +5 -5
- data/lib/action_dispatch/journey/nodes/node.rb +4 -0
- data/lib/action_dispatch/journey/parser.rb +51 -59
- data/lib/action_dispatch/journey/parser.y +12 -10
- data/lib/action_dispatch/journey/path/pattern.rb +16 -19
- data/lib/action_dispatch/journey/route.rb +8 -19
- data/lib/action_dispatch/journey/router/strexp.rb +9 -6
- data/lib/action_dispatch/journey/router/utils.rb +54 -18
- data/lib/action_dispatch/journey/router.rb +53 -75
- data/lib/action_dispatch/journey/routes.rb +4 -0
- data/lib/action_dispatch/journey/scanner.rb +5 -5
- data/lib/action_dispatch/journey/visitors.rb +81 -60
- data/lib/action_dispatch/journey/visualizer/fsm.css +0 -4
- data/lib/action_dispatch/journey/visualizer/index.html.erb +2 -2
- data/lib/action_dispatch/middleware/callbacks.rb +7 -7
- data/lib/action_dispatch/middleware/cookies.rb +119 -43
- data/lib/action_dispatch/middleware/debug_exceptions.rb +32 -13
- data/lib/action_dispatch/middleware/exception_wrapper.rb +60 -20
- data/lib/action_dispatch/middleware/flash.rb +37 -24
- data/lib/action_dispatch/middleware/params_parser.rb +2 -2
- data/lib/action_dispatch/middleware/public_exceptions.rb +12 -3
- data/lib/action_dispatch/middleware/reloader.rb +11 -2
- data/lib/action_dispatch/middleware/remote_ip.rb +40 -54
- data/lib/action_dispatch/middleware/request_id.rb +1 -1
- data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +8 -7
- data/lib/action_dispatch/middleware/show_exceptions.rb +6 -2
- data/lib/action_dispatch/middleware/ssl.rb +10 -7
- data/lib/action_dispatch/middleware/static.rb +79 -23
- data/lib/action_dispatch/middleware/templates/rescues/{_request_and_response.erb → _request_and_response.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +21 -19
- data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +52 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/{diagnostics.erb → diagnostics.html.erb} +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/rescues/{routing_error.erb → routing_error.html.erb} +3 -1
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.text.erb +11 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +20 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +7 -0
- data/lib/action_dispatch/middleware/templates/rescues/{unknown_action.erb → unknown_action.html.erb} +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +120 -64
- data/lib/action_dispatch/railtie.rb +5 -2
- data/lib/action_dispatch/request/session.rb +12 -0
- data/lib/action_dispatch/request/utils.rb +35 -0
- data/lib/action_dispatch/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +11 -17
- data/lib/action_dispatch/routing/mapper.rb +519 -312
- data/lib/action_dispatch/routing/polymorphic_routes.rb +204 -79
- data/lib/action_dispatch/routing/redirection.rb +51 -26
- data/lib/action_dispatch/routing/route_set.rb +331 -206
- data/lib/action_dispatch/routing/routes_proxy.rb +5 -4
- data/lib/action_dispatch/routing/url_for.rb +19 -5
- data/lib/action_dispatch/routing.rb +9 -6
- data/lib/action_dispatch/testing/assertions/dom.rb +2 -26
- data/lib/action_dispatch/testing/assertions/response.rb +9 -15
- data/lib/action_dispatch/testing/assertions/routing.rb +22 -22
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -429
- data/lib/action_dispatch/testing/assertions/tag.rb +2 -134
- data/lib/action_dispatch/testing/assertions.rb +11 -7
- data/lib/action_dispatch/testing/integration.rb +31 -29
- data/lib/action_dispatch/testing/test_request.rb +1 -1
- data/lib/action_dispatch/testing/test_response.rb +1 -5
- data/lib/action_dispatch.rb +5 -8
- data/lib/action_pack/gem_version.rb +15 -0
- data/lib/action_pack/version.rb +4 -7
- data/lib/action_pack.rb +1 -1
- metadata +77 -159
- data/lib/abstract_controller/layouts.rb +0 -423
- data/lib/abstract_controller/view_paths.rb +0 -96
- data/lib/action_controller/deprecated/integration_test.rb +0 -5
- data/lib/action_controller/deprecated.rb +0 -7
- data/lib/action_controller/metal/responder.rb +0 -287
- data/lib/action_controller/record_identifier.rb +0 -31
- data/lib/action_controller/vendor/html-scanner.rb +0 -5
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -24
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -7
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -43
- data/lib/action_view/base.rb +0 -201
- data/lib/action_view/buffers.rb +0 -49
- data/lib/action_view/context.rb +0 -36
- data/lib/action_view/dependency_tracker.rb +0 -93
- data/lib/action_view/digestor.rb +0 -113
- data/lib/action_view/flows.rb +0 -76
- data/lib/action_view/helpers/active_model_helper.rb +0 -49
- data/lib/action_view/helpers/asset_tag_helper.rb +0 -320
- data/lib/action_view/helpers/asset_url_helper.rb +0 -355
- data/lib/action_view/helpers/atom_feed_helper.rb +0 -203
- data/lib/action_view/helpers/cache_helper.rb +0 -196
- data/lib/action_view/helpers/capture_helper.rb +0 -216
- data/lib/action_view/helpers/controller_helper.rb +0 -25
- data/lib/action_view/helpers/csrf_helper.rb +0 -30
- data/lib/action_view/helpers/date_helper.rb +0 -1083
- data/lib/action_view/helpers/debug_helper.rb +0 -39
- data/lib/action_view/helpers/form_helper.rb +0 -1880
- data/lib/action_view/helpers/form_options_helper.rb +0 -838
- data/lib/action_view/helpers/form_tag_helper.rb +0 -785
- data/lib/action_view/helpers/javascript_helper.rb +0 -117
- data/lib/action_view/helpers/number_helper.rb +0 -441
- data/lib/action_view/helpers/output_safety_helper.rb +0 -38
- data/lib/action_view/helpers/record_tag_helper.rb +0 -106
- data/lib/action_view/helpers/rendering_helper.rb +0 -90
- data/lib/action_view/helpers/sanitize_helper.rb +0 -256
- data/lib/action_view/helpers/tag_helper.rb +0 -173
- data/lib/action_view/helpers/tags/base.rb +0 -148
- data/lib/action_view/helpers/tags/check_box.rb +0 -64
- data/lib/action_view/helpers/tags/checkable.rb +0 -16
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +0 -44
- data/lib/action_view/helpers/tags/collection_helpers.rb +0 -84
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +0 -36
- data/lib/action_view/helpers/tags/collection_select.rb +0 -28
- data/lib/action_view/helpers/tags/color_field.rb +0 -25
- data/lib/action_view/helpers/tags/date_field.rb +0 -13
- data/lib/action_view/helpers/tags/date_select.rb +0 -72
- data/lib/action_view/helpers/tags/datetime_field.rb +0 -22
- data/lib/action_view/helpers/tags/datetime_local_field.rb +0 -19
- data/lib/action_view/helpers/tags/datetime_select.rb +0 -8
- data/lib/action_view/helpers/tags/email_field.rb +0 -8
- data/lib/action_view/helpers/tags/file_field.rb +0 -8
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +0 -29
- data/lib/action_view/helpers/tags/hidden_field.rb +0 -8
- data/lib/action_view/helpers/tags/label.rb +0 -66
- data/lib/action_view/helpers/tags/month_field.rb +0 -13
- data/lib/action_view/helpers/tags/number_field.rb +0 -18
- data/lib/action_view/helpers/tags/password_field.rb +0 -12
- data/lib/action_view/helpers/tags/radio_button.rb +0 -31
- data/lib/action_view/helpers/tags/range_field.rb +0 -8
- data/lib/action_view/helpers/tags/search_field.rb +0 -24
- data/lib/action_view/helpers/tags/select.rb +0 -40
- data/lib/action_view/helpers/tags/tel_field.rb +0 -8
- data/lib/action_view/helpers/tags/text_area.rb +0 -18
- data/lib/action_view/helpers/tags/text_field.rb +0 -29
- data/lib/action_view/helpers/tags/time_field.rb +0 -13
- data/lib/action_view/helpers/tags/time_select.rb +0 -8
- data/lib/action_view/helpers/tags/time_zone_select.rb +0 -20
- data/lib/action_view/helpers/tags/url_field.rb +0 -8
- data/lib/action_view/helpers/tags/week_field.rb +0 -13
- data/lib/action_view/helpers/tags.rb +0 -39
- data/lib/action_view/helpers/text_helper.rb +0 -443
- data/lib/action_view/helpers/translation_helper.rb +0 -107
- data/lib/action_view/helpers/url_helper.rb +0 -635
- data/lib/action_view/helpers.rb +0 -58
- data/lib/action_view/locale/en.yml +0 -56
- data/lib/action_view/log_subscriber.rb +0 -30
- data/lib/action_view/lookup_context.rb +0 -241
- data/lib/action_view/model_naming.rb +0 -12
- data/lib/action_view/path_set.rb +0 -77
- data/lib/action_view/railtie.rb +0 -43
- data/lib/action_view/record_identifier.rb +0 -84
- data/lib/action_view/renderer/abstract_renderer.rb +0 -47
- data/lib/action_view/renderer/partial_renderer.rb +0 -492
- data/lib/action_view/renderer/renderer.rb +0 -50
- data/lib/action_view/renderer/streaming_template_renderer.rb +0 -103
- data/lib/action_view/renderer/template_renderer.rb +0 -96
- data/lib/action_view/routing_url_for.rb +0 -107
- data/lib/action_view/tasks/dependencies.rake +0 -17
- data/lib/action_view/template/error.rb +0 -138
- data/lib/action_view/template/handlers/builder.rb +0 -26
- data/lib/action_view/template/handlers/erb.rb +0 -146
- data/lib/action_view/template/handlers/raw.rb +0 -11
- data/lib/action_view/template/handlers.rb +0 -53
- data/lib/action_view/template/resolver.rb +0 -326
- data/lib/action_view/template/text.rb +0 -34
- data/lib/action_view/template/types.rb +0 -57
- data/lib/action_view/template.rb +0 -339
- data/lib/action_view/test_case.rb +0 -270
- data/lib/action_view/testing/resolvers.rb +0 -50
- data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
- data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_view/vendor/html-scanner.rb +0 -20
- data/lib/action_view.rb +0 -93
@@ -3,132 +3,203 @@ require 'active_support/core_ext/hash/reverse_merge'
|
|
3
3
|
require 'active_support/core_ext/hash/slice'
|
4
4
|
require 'active_support/core_ext/enumerable'
|
5
5
|
require 'active_support/core_ext/array/extract_options'
|
6
|
+
require 'active_support/core_ext/module/remove_method'
|
7
|
+
require 'active_support/core_ext/string/filters'
|
6
8
|
require 'active_support/inflector'
|
7
9
|
require 'action_dispatch/routing/redirection'
|
10
|
+
require 'action_dispatch/routing/endpoint'
|
11
|
+
require 'active_support/deprecation'
|
8
12
|
|
9
13
|
module ActionDispatch
|
10
14
|
module Routing
|
11
15
|
class Mapper
|
12
16
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
13
|
-
SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
14
|
-
:controller, :action, :path_names, :constraints,
|
15
|
-
:shallow, :blocks, :defaults, :options]
|
16
|
-
|
17
|
-
class Constraints #:nodoc:
|
18
|
-
def self.new(app, constraints, request = Rack::Request)
|
19
|
-
if constraints.any?
|
20
|
-
super(app, constraints, request)
|
21
|
-
else
|
22
|
-
app
|
23
|
-
end
|
24
|
-
end
|
25
17
|
|
18
|
+
class Constraints < Endpoint #:nodoc:
|
26
19
|
attr_reader :app, :constraints
|
27
20
|
|
28
|
-
def initialize(app, constraints,
|
29
|
-
|
21
|
+
def initialize(app, constraints, dispatcher_p)
|
22
|
+
# Unwrap Constraints objects. I don't actually think it's possible
|
23
|
+
# to pass a Constraints object to this constructor, but there were
|
24
|
+
# multiple places that kept testing children of this object. I
|
25
|
+
# *think* they were just being defensive, but I have no idea.
|
26
|
+
if app.is_a?(self.class)
|
27
|
+
constraints += app.constraints
|
28
|
+
app = app.app
|
29
|
+
end
|
30
|
+
|
31
|
+
@dispatcher = dispatcher_p
|
32
|
+
|
33
|
+
@app, @constraints, = app, constraints
|
30
34
|
end
|
31
35
|
|
32
|
-
def
|
33
|
-
req = @request.new(env)
|
36
|
+
def dispatcher?; @dispatcher; end
|
34
37
|
|
38
|
+
def matches?(req)
|
35
39
|
@constraints.all? do |constraint|
|
36
40
|
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
|
37
41
|
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
|
38
42
|
end
|
39
|
-
ensure
|
40
|
-
req.reset_parameters
|
41
43
|
end
|
42
44
|
|
43
|
-
def
|
44
|
-
|
45
|
+
def serve(req)
|
46
|
+
return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
|
47
|
+
|
48
|
+
if dispatcher?
|
49
|
+
@app.serve req
|
50
|
+
else
|
51
|
+
@app.call req.env
|
52
|
+
end
|
45
53
|
end
|
46
54
|
|
47
55
|
private
|
48
56
|
def constraint_args(constraint, request)
|
49
|
-
constraint.arity == 1 ? [request] : [request.
|
57
|
+
constraint.arity == 1 ? [request] : [request.path_parameters, request]
|
50
58
|
end
|
51
59
|
end
|
52
60
|
|
53
61
|
class Mapping #:nodoc:
|
54
|
-
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
|
55
62
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
56
|
-
|
63
|
+
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
|
+
|
65
|
+
attr_reader :requirements, :conditions, :defaults
|
66
|
+
attr_reader :to, :default_controller, :default_action, :as, :anchor
|
67
|
+
|
68
|
+
def self.build(scope, set, path, as, options)
|
69
|
+
options = scope[:options].merge(options) if scope[:options]
|
57
70
|
|
58
|
-
|
71
|
+
options.delete :only
|
72
|
+
options.delete :except
|
73
|
+
options.delete :shallow_path
|
74
|
+
options.delete :shallow_prefix
|
75
|
+
options.delete :shallow
|
59
76
|
|
60
|
-
|
61
|
-
@set, @scope, @path, @options = set, scope, path, options
|
62
|
-
@requirements, @conditions, @defaults = {}, {}, {}
|
77
|
+
defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
|
63
78
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
79
|
+
new scope, set, path, defaults, as, options
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(scope, set, path, defaults, as, options)
|
83
|
+
@requirements, @conditions = {}, {}
|
84
|
+
@defaults = defaults
|
85
|
+
@set = set
|
86
|
+
|
87
|
+
@to = options.delete :to
|
88
|
+
@default_controller = options.delete(:controller) || scope[:controller]
|
89
|
+
@default_action = options.delete(:action) || scope[:action]
|
90
|
+
@as = as
|
91
|
+
@anchor = options.delete :anchor
|
92
|
+
|
93
|
+
formatted = options.delete :format
|
94
|
+
via = Array(options.delete(:via) { [] })
|
95
|
+
options_constraints = options.delete :constraints
|
96
|
+
|
97
|
+
path = normalize_path! path, formatted
|
98
|
+
ast = path_ast path
|
99
|
+
path_params = path_params ast
|
100
|
+
|
101
|
+
options = normalize_options!(options, formatted, path_params, ast, scope[:module])
|
102
|
+
|
103
|
+
|
104
|
+
split_constraints(path_params, scope[:constraints]) if scope[:constraints]
|
105
|
+
constraints = constraints(options, path_params)
|
106
|
+
|
107
|
+
split_constraints path_params, constraints
|
108
|
+
|
109
|
+
@blocks = blocks(options_constraints, scope[:blocks])
|
110
|
+
|
111
|
+
if options_constraints.is_a?(Hash)
|
112
|
+
split_constraints path_params, options_constraints
|
113
|
+
options_constraints.each do |key, default|
|
114
|
+
if URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
115
|
+
@defaults[key] ||= default
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
normalize_format!(formatted)
|
121
|
+
|
122
|
+
@conditions[:path_info] = path
|
123
|
+
@conditions[:parsed_path_info] = ast
|
124
|
+
|
125
|
+
add_request_method(via, @conditions)
|
126
|
+
normalize_defaults!(options)
|
69
127
|
end
|
70
128
|
|
71
129
|
def to_route
|
72
|
-
[ app, conditions, requirements, defaults,
|
130
|
+
[ app(@blocks), conditions, requirements, defaults, as, anchor ]
|
73
131
|
end
|
74
132
|
|
75
133
|
private
|
76
134
|
|
77
|
-
def normalize_path!
|
78
|
-
|
79
|
-
@path = Mapper.normalize_path(@path)
|
135
|
+
def normalize_path!(path, format)
|
136
|
+
path = Mapper.normalize_path(path)
|
80
137
|
|
81
|
-
if
|
82
|
-
|
83
|
-
elsif optional_format?
|
84
|
-
|
138
|
+
if format == true
|
139
|
+
"#{path}.:format"
|
140
|
+
elsif optional_format?(path, format)
|
141
|
+
"#{path}(.:format)"
|
142
|
+
else
|
143
|
+
path
|
85
144
|
end
|
86
145
|
end
|
87
146
|
|
88
|
-
def
|
89
|
-
|
90
|
-
end
|
91
|
-
|
92
|
-
def optional_format?
|
93
|
-
options[:format] != false && !path.include?(':format') && !path.end_with?('/')
|
147
|
+
def optional_format?(path, format)
|
148
|
+
format != false && path !~ OPTIONAL_FORMAT_REGEX
|
94
149
|
end
|
95
150
|
|
96
|
-
def normalize_options!
|
97
|
-
@options.reverse_merge!(scope[:options]) if scope[:options]
|
98
|
-
path_without_format = path.sub(/\(\.:format\)$/, '')
|
99
|
-
|
151
|
+
def normalize_options!(options, formatted, path_params, path_ast, modyoule)
|
100
152
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
101
153
|
# optional format part of the route by default
|
102
|
-
if
|
103
|
-
|
154
|
+
if formatted != false
|
155
|
+
path_ast.grep(Journey::Nodes::Star) do |node|
|
156
|
+
options[node.name.to_sym] ||= /.+?/
|
157
|
+
end
|
104
158
|
end
|
105
159
|
|
106
|
-
if
|
107
|
-
raise ArgumentError, ":controller segment is not allowed within a namespace block" if
|
160
|
+
if path_params.include?(:controller)
|
161
|
+
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
108
162
|
|
109
163
|
# Add a default constraint for :controller path segments that matches namespaced
|
110
164
|
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
111
165
|
# GET /admin/products/show/1
|
112
166
|
# => { controller: 'admin/products', action: 'show', id: '1' }
|
113
|
-
|
167
|
+
options[:controller] ||= /.+?/
|
114
168
|
end
|
115
169
|
|
116
|
-
|
170
|
+
if to.respond_to? :call
|
171
|
+
options
|
172
|
+
else
|
173
|
+
to_endpoint = split_to to
|
174
|
+
controller = to_endpoint[0] || default_controller
|
175
|
+
action = to_endpoint[1] || default_action
|
176
|
+
|
177
|
+
controller = add_controller_module(controller, modyoule)
|
178
|
+
|
179
|
+
options.merge! check_controller_and_action(path_params, controller, action)
|
180
|
+
end
|
117
181
|
end
|
118
182
|
|
119
|
-
def
|
120
|
-
constraints.
|
121
|
-
|
122
|
-
|
123
|
-
|
183
|
+
def split_constraints(path_params, constraints)
|
184
|
+
constraints.each_pair do |key, requirement|
|
185
|
+
if path_params.include?(key) || key == :controller
|
186
|
+
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
187
|
+
@requirements[key] = requirement
|
188
|
+
else
|
189
|
+
@conditions[key] = requirement
|
190
|
+
end
|
124
191
|
end
|
192
|
+
end
|
125
193
|
|
126
|
-
|
194
|
+
def normalize_format!(formatted)
|
195
|
+
if formatted == true
|
127
196
|
@requirements[:format] ||= /.+/
|
128
|
-
elsif Regexp ===
|
129
|
-
@requirements[:format] =
|
130
|
-
|
131
|
-
|
197
|
+
elsif Regexp === formatted
|
198
|
+
@requirements[:format] = formatted
|
199
|
+
@defaults[:format] = nil
|
200
|
+
elsif String === formatted
|
201
|
+
@requirements[:format] = Regexp.compile(formatted)
|
202
|
+
@defaults[:format] = formatted
|
132
203
|
end
|
133
204
|
end
|
134
205
|
|
@@ -142,160 +213,147 @@ module ActionDispatch
|
|
142
213
|
end
|
143
214
|
end
|
144
215
|
|
145
|
-
def normalize_defaults!
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
options.each do |key, default|
|
150
|
-
next if Regexp === default || IGNORE_OPTIONS.include?(key)
|
151
|
-
@defaults[key] = default
|
152
|
-
end
|
153
|
-
|
154
|
-
if options[:constraints].is_a?(Hash)
|
155
|
-
options[:constraints].each do |key, default|
|
156
|
-
next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
|
157
|
-
@defaults[key] ||= default
|
216
|
+
def normalize_defaults!(options)
|
217
|
+
options.each_pair do |key, default|
|
218
|
+
unless Regexp === default
|
219
|
+
@defaults[key] = default
|
158
220
|
end
|
159
221
|
end
|
160
|
-
|
161
|
-
if Regexp === options[:format]
|
162
|
-
@defaults[:format] = nil
|
163
|
-
elsif String === options[:format]
|
164
|
-
@defaults[:format] = options[:format]
|
165
|
-
end
|
166
222
|
end
|
167
223
|
|
168
|
-
def
|
169
|
-
|
170
|
-
|
171
|
-
constraints.each do |key, condition|
|
172
|
-
next if segment_keys.include?(key) || key == :controller
|
173
|
-
@conditions[key] = condition
|
174
|
-
end
|
175
|
-
|
176
|
-
@conditions[:required_defaults] = []
|
177
|
-
options.each do |key, required_default|
|
178
|
-
next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
|
179
|
-
next if Regexp === required_default
|
180
|
-
@conditions[:required_defaults] << key
|
224
|
+
def verify_callable_constraint(callable_constraint)
|
225
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
226
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
181
227
|
end
|
228
|
+
end
|
182
229
|
|
183
|
-
|
230
|
+
def add_request_method(via, conditions)
|
231
|
+
return if via == [:all]
|
184
232
|
|
185
|
-
if
|
233
|
+
if via.empty?
|
186
234
|
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
187
235
|
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
188
236
|
"If you want to expose your action to GET, use `get` in the router:\n" \
|
189
237
|
" Instead of: match \"controller#action\"\n" \
|
190
238
|
" Do: get \"controller#action\""
|
191
|
-
raise msg
|
192
|
-
end
|
193
|
-
|
194
|
-
if via = options[:via]
|
195
|
-
list = Array(via).map { |m| m.to_s.dasherize.upcase }
|
196
|
-
@conditions.merge!(:request_method => list)
|
239
|
+
raise ArgumentError, msg
|
197
240
|
end
|
198
|
-
end
|
199
241
|
|
200
|
-
|
201
|
-
Constraints.new(endpoint, blocks, @set.request_class)
|
242
|
+
conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
|
202
243
|
end
|
203
244
|
|
204
|
-
def
|
245
|
+
def app(blocks)
|
205
246
|
if to.respond_to?(:call)
|
206
|
-
|
247
|
+
Constraints.new(to, blocks, false)
|
248
|
+
elsif blocks.any?
|
249
|
+
Constraints.new(dispatcher(defaults), blocks, true)
|
207
250
|
else
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
action = to.to_s
|
212
|
-
end
|
213
|
-
|
214
|
-
controller ||= default_controller
|
215
|
-
action ||= default_action
|
216
|
-
|
217
|
-
unless controller.is_a?(Regexp)
|
218
|
-
controller = [@scope[:module], controller].compact.join("/").presence
|
219
|
-
end
|
220
|
-
|
221
|
-
if controller.is_a?(String) && controller =~ %r{\A/}
|
222
|
-
raise ArgumentError, "controller name should not start with a slash"
|
223
|
-
end
|
251
|
+
dispatcher(defaults)
|
252
|
+
end
|
253
|
+
end
|
224
254
|
|
225
|
-
|
226
|
-
|
255
|
+
def check_controller_and_action(path_params, controller, action)
|
256
|
+
hash = check_part(:controller, controller, path_params, {}) do |part|
|
257
|
+
translate_controller(part) {
|
258
|
+
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
259
|
+
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
227
260
|
|
228
|
-
|
229
|
-
|
230
|
-
|
261
|
+
raise ArgumentError, message
|
262
|
+
}
|
263
|
+
end
|
231
264
|
|
232
|
-
|
233
|
-
|
234
|
-
|
265
|
+
check_part(:action, action, path_params, hash) { |part|
|
266
|
+
part.is_a?(Regexp) ? part : part.to_s
|
267
|
+
}
|
268
|
+
end
|
235
269
|
|
236
|
-
|
237
|
-
|
238
|
-
|
270
|
+
def check_part(name, part, path_params, hash)
|
271
|
+
if part
|
272
|
+
hash[name] = yield(part)
|
273
|
+
else
|
274
|
+
unless path_params.include?(name)
|
275
|
+
message = "Missing :#{name} key on routes definition, please check your routes."
|
239
276
|
raise ArgumentError, message
|
240
277
|
end
|
241
|
-
|
242
|
-
hash = {}
|
243
|
-
hash[:controller] = controller unless controller.blank?
|
244
|
-
hash[:action] = action unless action.blank?
|
245
|
-
hash
|
246
278
|
end
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
279
|
+
hash
|
280
|
+
end
|
281
|
+
|
282
|
+
def split_to(to)
|
283
|
+
case to
|
284
|
+
when Symbol
|
285
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
286
|
+
Defining a route where `to` is a symbol is deprecated.
|
287
|
+
Please change `to: :#{to}` to `action: :#{to}`.
|
288
|
+
MSG
|
289
|
+
|
290
|
+
[nil, to.to_s]
|
291
|
+
when /#/ then to.split('#')
|
292
|
+
when String
|
293
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
294
|
+
Defining a route where `to` is a controller without an action is deprecated.
|
295
|
+
Please change `to: '#{to}'` to `controller: '#{to}'`.
|
296
|
+
MSG
|
297
|
+
|
298
|
+
[to, nil]
|
252
299
|
else
|
253
|
-
|
300
|
+
[]
|
254
301
|
end
|
255
302
|
end
|
256
303
|
|
257
|
-
def
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
304
|
+
def add_controller_module(controller, modyoule)
|
305
|
+
if modyoule && !controller.is_a?(Regexp)
|
306
|
+
if controller =~ %r{\A/}
|
307
|
+
controller[1..-1]
|
308
|
+
else
|
309
|
+
[modyoule, controller].compact.join("/")
|
263
310
|
end
|
264
|
-
|
265
|
-
|
311
|
+
else
|
312
|
+
controller
|
266
313
|
end
|
267
314
|
end
|
268
315
|
|
269
|
-
def
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
def path_pattern
|
274
|
-
Journey::Path::Pattern.new(strexp)
|
275
|
-
end
|
316
|
+
def translate_controller(controller)
|
317
|
+
return controller if Regexp === controller
|
318
|
+
return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
|
276
319
|
|
277
|
-
|
278
|
-
Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
|
320
|
+
yield
|
279
321
|
end
|
280
322
|
|
281
|
-
def
|
282
|
-
|
323
|
+
def blocks(options_constraints, scope_blocks)
|
324
|
+
if options_constraints && !options_constraints.is_a?(Hash)
|
325
|
+
verify_callable_constraint(options_constraints)
|
326
|
+
[options_constraints]
|
327
|
+
else
|
328
|
+
scope_blocks || []
|
329
|
+
end
|
283
330
|
end
|
284
331
|
|
285
|
-
def
|
286
|
-
|
332
|
+
def constraints(options, path_params)
|
333
|
+
constraints = {}
|
334
|
+
required_defaults = []
|
335
|
+
options.each_pair do |key, option|
|
336
|
+
if Regexp === option
|
337
|
+
constraints[key] = option
|
338
|
+
else
|
339
|
+
required_defaults << key unless path_params.include?(key)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
@conditions[:required_defaults] = required_defaults
|
343
|
+
constraints
|
287
344
|
end
|
288
345
|
|
289
|
-
def
|
290
|
-
|
346
|
+
def path_params(ast)
|
347
|
+
ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
|
291
348
|
end
|
292
349
|
|
293
|
-
def
|
294
|
-
|
350
|
+
def path_ast(path)
|
351
|
+
parser = Journey::Parser.new
|
352
|
+
parser.parse path
|
295
353
|
end
|
296
354
|
|
297
|
-
def
|
298
|
-
|
355
|
+
def dispatcher(defaults)
|
356
|
+
@set.dispatcher defaults
|
299
357
|
end
|
300
358
|
end
|
301
359
|
|
@@ -330,40 +388,61 @@ module ActionDispatch
|
|
330
388
|
match '/', { :as => :root, :via => :get }.merge!(options)
|
331
389
|
end
|
332
390
|
|
333
|
-
# Matches a url pattern to one or more routes.
|
334
|
-
#
|
335
|
-
# in
|
391
|
+
# Matches a url pattern to one or more routes.
|
392
|
+
#
|
393
|
+
# You should not use the +match+ method in your router
|
394
|
+
# without specifying an HTTP method.
|
395
|
+
#
|
396
|
+
# If you want to expose your action to both GET and POST, use:
|
336
397
|
#
|
337
398
|
# # sets :controller, :action and :id in params
|
338
|
-
# match ':controller/:action/:id'
|
399
|
+
# match ':controller/:action/:id', via: [:get, :post]
|
400
|
+
#
|
401
|
+
# Note that +:controller+, +:action+ and +:id+ are interpreted as url
|
402
|
+
# query parameters and thus available through +params+ in an action.
|
403
|
+
#
|
404
|
+
# If you want to expose your action to GET, use +get+ in the router:
|
405
|
+
#
|
406
|
+
# Instead of:
|
407
|
+
#
|
408
|
+
# match ":controller/:action/:id"
|
409
|
+
#
|
410
|
+
# Do:
|
411
|
+
#
|
412
|
+
# get ":controller/:action/:id"
|
339
413
|
#
|
340
414
|
# Two of these symbols are special, +:controller+ maps to the controller
|
341
415
|
# and +:action+ to the controller's action. A pattern can also map
|
342
416
|
# wildcard segments (globs) to params:
|
343
417
|
#
|
344
|
-
#
|
418
|
+
# get 'songs/*category/:title', to: 'songs#show'
|
345
419
|
#
|
346
420
|
# # 'songs/rock/classic/stairway-to-heaven' sets
|
347
421
|
# # params[:category] = 'rock/classic'
|
348
422
|
# # params[:title] = 'stairway-to-heaven'
|
349
423
|
#
|
424
|
+
# To match a wildcard parameter, it must have a name assigned to it.
|
425
|
+
# Without a variable name to attach the glob parameter to, the route
|
426
|
+
# can't be parsed.
|
427
|
+
#
|
350
428
|
# When a pattern points to an internal route, the route's +:action+ and
|
351
429
|
# +:controller+ should be set in options or hash shorthand. Examples:
|
352
430
|
#
|
353
|
-
# match 'photos/:id' => 'photos#show'
|
354
|
-
# match 'photos/:id', to: 'photos#show'
|
355
|
-
# match 'photos/:id', controller: 'photos', action: 'show'
|
431
|
+
# match 'photos/:id' => 'photos#show', via: :get
|
432
|
+
# match 'photos/:id', to: 'photos#show', via: :get
|
433
|
+
# match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
356
434
|
#
|
357
435
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
358
436
|
# responds to +call+:
|
359
437
|
#
|
360
|
-
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
|
361
|
-
# match 'photos/:id', to: PhotoRackApp
|
438
|
+
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
|
439
|
+
# match 'photos/:id', to: PhotoRackApp, via: :get
|
362
440
|
# # Yes, controller actions are just rack endpoints
|
363
|
-
# match 'photos/:id', to: PhotosController.action(:show)
|
441
|
+
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
364
442
|
#
|
365
|
-
# Because
|
366
|
-
# implications,
|
443
|
+
# Because requesting various HTTP verbs with a single action has security
|
444
|
+
# implications, you must either specify the actions in
|
445
|
+
# the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
|
367
446
|
# instead +match+
|
368
447
|
#
|
369
448
|
# === Options
|
@@ -376,14 +455,20 @@ module ActionDispatch
|
|
376
455
|
# [:action]
|
377
456
|
# The route's action.
|
378
457
|
#
|
458
|
+
# [:param]
|
459
|
+
# Overrides the default resource identifier +:id+ (name of the
|
460
|
+
# dynamic segment used to generate the routes).
|
461
|
+
# You can access that segment from your controller using
|
462
|
+
# <tt>params[<:param>]</tt>.
|
463
|
+
#
|
379
464
|
# [:path]
|
380
465
|
# The path prefix for the routes.
|
381
466
|
#
|
382
467
|
# [:module]
|
383
468
|
# The namespace for :controller.
|
384
469
|
#
|
385
|
-
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
|
386
|
-
#
|
470
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
|
471
|
+
# # => Sekret::PostsController
|
387
472
|
#
|
388
473
|
# See <tt>Scoping#namespace</tt> for its scope equivalent.
|
389
474
|
#
|
@@ -401,9 +486,9 @@ module ActionDispatch
|
|
401
486
|
# Points to a +Rack+ endpoint. Can be an object that responds to
|
402
487
|
# +call+ or a string representing a controller's action.
|
403
488
|
#
|
404
|
-
# match 'path', to: 'controller#action'
|
405
|
-
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
|
406
|
-
# match 'path', to: RackApp
|
489
|
+
# match 'path', to: 'controller#action', via: :get
|
490
|
+
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
|
491
|
+
# match 'path', to: RackApp, via: :get
|
407
492
|
#
|
408
493
|
# [:on]
|
409
494
|
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
@@ -428,14 +513,14 @@ module ActionDispatch
|
|
428
513
|
# other than path can also be specified with any object
|
429
514
|
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
|
430
515
|
#
|
431
|
-
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
|
516
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
432
517
|
#
|
433
|
-
# match 'json_only', constraints: { format: 'json' }
|
518
|
+
# match 'json_only', constraints: { format: 'json' }, via: :get
|
434
519
|
#
|
435
|
-
# class
|
520
|
+
# class Whitelist
|
436
521
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
437
522
|
# end
|
438
|
-
# match 'path', to: 'c#a', constraints:
|
523
|
+
# match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
|
439
524
|
#
|
440
525
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
441
526
|
# equivalent.
|
@@ -444,7 +529,7 @@ module ActionDispatch
|
|
444
529
|
# Sets defaults for parameters
|
445
530
|
#
|
446
531
|
# # Sets params[:format] to 'jpg' by default
|
447
|
-
# match 'path', to: 'c#a', defaults: { format: 'jpg' }
|
532
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
|
448
533
|
#
|
449
534
|
# See <tt>Scoping#defaults</tt> for its scope equivalent.
|
450
535
|
#
|
@@ -453,7 +538,7 @@ module ActionDispatch
|
|
453
538
|
# false, the pattern matches any request prefixed with the given path.
|
454
539
|
#
|
455
540
|
# # Matches any request starting with 'path'
|
456
|
-
# match 'path', to: 'c#a', anchor: false
|
541
|
+
# match 'path', to: 'c#a', anchor: false, via: :get
|
457
542
|
#
|
458
543
|
# [:format]
|
459
544
|
# Allows you to specify the default value for optional +format+
|
@@ -495,12 +580,15 @@ module ActionDispatch
|
|
495
580
|
|
496
581
|
raise "A rack application must be specified" unless path
|
497
582
|
|
498
|
-
|
583
|
+
rails_app = rails_app? app
|
584
|
+
options[:as] ||= app_name(app, rails_app)
|
585
|
+
|
586
|
+
target_as = name_for_action(options[:as], path)
|
499
587
|
options[:via] ||= :all
|
500
588
|
|
501
589
|
match(path, options.merge(:to => app, :anchor => false, :format => false))
|
502
590
|
|
503
|
-
define_generate_prefix(app,
|
591
|
+
define_generate_prefix(app, target_as) if rails_app
|
504
592
|
self
|
505
593
|
end
|
506
594
|
|
@@ -521,35 +609,37 @@ module ActionDispatch
|
|
521
609
|
end
|
522
610
|
|
523
611
|
private
|
524
|
-
def
|
525
|
-
|
612
|
+
def rails_app?(app)
|
613
|
+
app.is_a?(Class) && app < Rails::Railtie
|
614
|
+
end
|
526
615
|
|
527
|
-
|
616
|
+
def app_name(app, rails_app)
|
617
|
+
if rails_app
|
528
618
|
app.railtie_name
|
529
|
-
|
530
|
-
class_name = app.
|
619
|
+
elsif app.is_a?(Class)
|
620
|
+
class_name = app.name
|
531
621
|
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
|
532
622
|
end
|
533
623
|
end
|
534
624
|
|
535
625
|
def define_generate_prefix(app, name)
|
536
|
-
|
537
|
-
|
538
|
-
_route = @set.named_routes.routes[name.to_sym]
|
626
|
+
_route = @set.named_routes.get name
|
539
627
|
_routes = @set
|
540
628
|
app.routes.define_mounted_helper(name)
|
541
|
-
app.routes.
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
629
|
+
app.routes.extend Module.new {
|
630
|
+
def optimize_routes_generation?; false; end
|
631
|
+
define_method :find_script_name do |options|
|
632
|
+
if options.key? :script_name
|
633
|
+
super(options)
|
634
|
+
else
|
635
|
+
prefix_options = options.slice(*_route.segment_keys)
|
636
|
+
prefix_options[:relative_url_root] = ''.freeze
|
637
|
+
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
638
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
639
|
+
_routes.url_helpers.send("#{name}_path", prefix_options)
|
640
|
+
end
|
551
641
|
end
|
552
|
-
|
642
|
+
}
|
553
643
|
end
|
554
644
|
end
|
555
645
|
|
@@ -636,7 +726,7 @@ module ActionDispatch
|
|
636
726
|
# resources :posts, module: "admin"
|
637
727
|
#
|
638
728
|
# If you want to route /admin/posts to +PostsController+
|
639
|
-
# (without the Admin
|
729
|
+
# (without the <tt>Admin::</tt> module prefix), you could use
|
640
730
|
#
|
641
731
|
# scope "/admin" do
|
642
732
|
# resources :posts
|
@@ -690,14 +780,19 @@ module ActionDispatch
|
|
690
780
|
# end
|
691
781
|
def scope(*args)
|
692
782
|
options = args.extract_options!.dup
|
693
|
-
|
783
|
+
scope = {}
|
694
784
|
|
695
785
|
options[:path] = args.flatten.join('/') if args.any?
|
696
786
|
options[:constraints] ||= {}
|
697
787
|
|
788
|
+
unless nested_scope?
|
789
|
+
options[:shallow_path] ||= options[:path] if options.key?(:path)
|
790
|
+
options[:shallow_prefix] ||= options[:as] if options.key?(:as)
|
791
|
+
end
|
792
|
+
|
698
793
|
if options[:constraints].is_a?(Hash)
|
699
|
-
defaults = options[:constraints].select do
|
700
|
-
|
794
|
+
defaults = options[:constraints].select do |k, v|
|
795
|
+
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
701
796
|
end
|
702
797
|
|
703
798
|
(options[:defaults] ||= {}).reverse_merge!(defaults)
|
@@ -705,7 +800,7 @@ module ActionDispatch
|
|
705
800
|
block, options[:constraints] = options[:constraints], {}
|
706
801
|
end
|
707
802
|
|
708
|
-
|
803
|
+
@scope.options.each do |option|
|
709
804
|
if option == :blocks
|
710
805
|
value = block
|
711
806
|
elsif option == :options
|
@@ -715,15 +810,15 @@ module ActionDispatch
|
|
715
810
|
end
|
716
811
|
|
717
812
|
if value
|
718
|
-
|
719
|
-
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
813
|
+
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
720
814
|
end
|
721
815
|
end
|
722
816
|
|
817
|
+
@scope = @scope.new scope
|
723
818
|
yield
|
724
819
|
self
|
725
820
|
ensure
|
726
|
-
@scope.
|
821
|
+
@scope = @scope.parent
|
727
822
|
end
|
728
823
|
|
729
824
|
# Scopes routes to a specific controller
|
@@ -776,9 +871,16 @@ module ActionDispatch
|
|
776
871
|
# end
|
777
872
|
def namespace(path, options = {})
|
778
873
|
path = path.to_s
|
779
|
-
|
780
|
-
|
781
|
-
|
874
|
+
|
875
|
+
defaults = {
|
876
|
+
module: path,
|
877
|
+
path: options.fetch(:path, path),
|
878
|
+
as: options.fetch(:as, path),
|
879
|
+
shallow_path: options.fetch(:path, path),
|
880
|
+
shallow_prefix: options.fetch(:as, path)
|
881
|
+
}
|
882
|
+
|
883
|
+
scope(defaults.merge!(options)) { yield }
|
782
884
|
end
|
783
885
|
|
784
886
|
# === Parameter Restriction
|
@@ -954,8 +1056,6 @@ module ActionDispatch
|
|
954
1056
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
955
1057
|
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
956
1058
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
957
|
-
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
958
|
-
RESOURCE_SCOPES = [:resource, :resources]
|
959
1059
|
|
960
1060
|
class Resource #:nodoc:
|
961
1061
|
attr_reader :controller, :path, :options, :param
|
@@ -967,6 +1067,7 @@ module ActionDispatch
|
|
967
1067
|
@as = options[:as]
|
968
1068
|
@param = (options[:param] || :id).to_sym
|
969
1069
|
@options = options
|
1070
|
+
@shallow = false
|
970
1071
|
end
|
971
1072
|
|
972
1073
|
def default_actions
|
@@ -1027,6 +1128,13 @@ module ActionDispatch
|
|
1027
1128
|
"#{path}/:#{nested_param}"
|
1028
1129
|
end
|
1029
1130
|
|
1131
|
+
def shallow=(value)
|
1132
|
+
@shallow = value
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
def shallow?
|
1136
|
+
@shallow
|
1137
|
+
end
|
1030
1138
|
end
|
1031
1139
|
|
1032
1140
|
class SingletonResource < Resource #:nodoc:
|
@@ -1066,18 +1174,18 @@ module ActionDispatch
|
|
1066
1174
|
# a singular resource to map /profile (rather than /profile/:id) to
|
1067
1175
|
# the show action:
|
1068
1176
|
#
|
1069
|
-
# resource :
|
1177
|
+
# resource :profile
|
1070
1178
|
#
|
1071
1179
|
# creates six different routes in your application, all mapping to
|
1072
|
-
# the +
|
1180
|
+
# the +Profiles+ controller (note that the controller is named after
|
1073
1181
|
# the plural):
|
1074
1182
|
#
|
1075
|
-
# GET /
|
1076
|
-
# POST /
|
1077
|
-
# GET /
|
1078
|
-
# GET /
|
1079
|
-
# PATCH/PUT /
|
1080
|
-
# DELETE /
|
1183
|
+
# GET /profile/new
|
1184
|
+
# POST /profile
|
1185
|
+
# GET /profile
|
1186
|
+
# GET /profile/edit
|
1187
|
+
# PATCH/PUT /profile
|
1188
|
+
# DELETE /profile
|
1081
1189
|
#
|
1082
1190
|
# === Options
|
1083
1191
|
# Takes same options as +resources+.
|
@@ -1307,8 +1415,10 @@ module ActionDispatch
|
|
1307
1415
|
end
|
1308
1416
|
|
1309
1417
|
with_scope_level(:member) do
|
1310
|
-
|
1311
|
-
yield
|
1418
|
+
if shallow?
|
1419
|
+
shallow_scope(parent_resource.member_scope) { yield }
|
1420
|
+
else
|
1421
|
+
scope(parent_resource.member_scope) { yield }
|
1312
1422
|
end
|
1313
1423
|
end
|
1314
1424
|
end
|
@@ -1331,16 +1441,8 @@ module ActionDispatch
|
|
1331
1441
|
end
|
1332
1442
|
|
1333
1443
|
with_scope_level(:nested) do
|
1334
|
-
if shallow?
|
1335
|
-
|
1336
|
-
if @scope[:shallow_path].blank?
|
1337
|
-
scope(parent_resource.nested_scope, nested_options) { yield }
|
1338
|
-
else
|
1339
|
-
scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
|
1340
|
-
scope(parent_resource.nested_scope, nested_options) { yield }
|
1341
|
-
end
|
1342
|
-
end
|
1343
|
-
end
|
1444
|
+
if shallow? && shallow_nesting_depth >= 1
|
1445
|
+
shallow_scope(parent_resource.nested_scope, nested_options) { yield }
|
1344
1446
|
else
|
1345
1447
|
scope(parent_resource.nested_scope, nested_options) { yield }
|
1346
1448
|
end
|
@@ -1357,7 +1459,7 @@ module ActionDispatch
|
|
1357
1459
|
end
|
1358
1460
|
|
1359
1461
|
def shallow
|
1360
|
-
scope(:shallow => true
|
1462
|
+
scope(:shallow => true) do
|
1361
1463
|
yield
|
1362
1464
|
end
|
1363
1465
|
end
|
@@ -1373,7 +1475,20 @@ module ActionDispatch
|
|
1373
1475
|
if rest.empty? && Hash === path
|
1374
1476
|
options = path
|
1375
1477
|
path, to = options.find { |name, _value| name.is_a?(String) }
|
1376
|
-
|
1478
|
+
|
1479
|
+
case to
|
1480
|
+
when Symbol
|
1481
|
+
options[:action] = to
|
1482
|
+
when String
|
1483
|
+
if to =~ /#/
|
1484
|
+
options[:to] = to
|
1485
|
+
else
|
1486
|
+
options[:controller] = to
|
1487
|
+
end
|
1488
|
+
else
|
1489
|
+
options[:to] = to
|
1490
|
+
end
|
1491
|
+
|
1377
1492
|
options.delete(path)
|
1378
1493
|
paths = [path]
|
1379
1494
|
else
|
@@ -1398,6 +1513,7 @@ module ActionDispatch
|
|
1398
1513
|
path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
|
1399
1514
|
if using_match_shorthand?(path_without_format, route_options)
|
1400
1515
|
route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
1516
|
+
route_options[:to].tr!("-", "_")
|
1401
1517
|
end
|
1402
1518
|
|
1403
1519
|
decomposed_match(_path, route_options)
|
@@ -1406,14 +1522,14 @@ module ActionDispatch
|
|
1406
1522
|
end
|
1407
1523
|
|
1408
1524
|
def using_match_shorthand?(path, options)
|
1409
|
-
path && (options[:to] || options[:action]).nil? && path =~ %r{
|
1525
|
+
path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1410
1526
|
end
|
1411
1527
|
|
1412
1528
|
def decomposed_match(path, options) # :nodoc:
|
1413
1529
|
if on = options.delete(:on)
|
1414
1530
|
send(on) { decomposed_match(path, options) }
|
1415
1531
|
else
|
1416
|
-
case @scope
|
1532
|
+
case @scope.scope_level
|
1417
1533
|
when :resources
|
1418
1534
|
nested { decomposed_match(path, options) }
|
1419
1535
|
when :resource
|
@@ -1426,21 +1542,23 @@ module ActionDispatch
|
|
1426
1542
|
|
1427
1543
|
def add_route(action, options) # :nodoc:
|
1428
1544
|
path = path_for_action(action, options.delete(:path))
|
1545
|
+
raise ArgumentError, "path is required" if path.blank?
|
1546
|
+
|
1429
1547
|
action = action.to_s.dup
|
1430
1548
|
|
1431
|
-
if action =~ /^[\w
|
1432
|
-
options[:action] ||= action unless action.include?("/")
|
1549
|
+
if action =~ /^[\w\-\/]+$/
|
1550
|
+
options[:action] ||= action.tr('-', '_') unless action.include?("/")
|
1433
1551
|
else
|
1434
1552
|
action = nil
|
1435
1553
|
end
|
1436
1554
|
|
1437
|
-
if !options.fetch(:as, true)
|
1438
|
-
|
1439
|
-
|
1440
|
-
|
1441
|
-
|
1555
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1556
|
+
options.delete(:as)
|
1557
|
+
else
|
1558
|
+
name_for_action(options.delete(:as), action)
|
1559
|
+
end
|
1442
1560
|
|
1443
|
-
mapping = Mapping.
|
1561
|
+
mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
|
1444
1562
|
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
1445
1563
|
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1446
1564
|
end
|
@@ -1454,7 +1572,7 @@ module ActionDispatch
|
|
1454
1572
|
raise ArgumentError, "must be called with a path and/or options"
|
1455
1573
|
end
|
1456
1574
|
|
1457
|
-
if @scope
|
1575
|
+
if @scope.resources?
|
1458
1576
|
with_scope_level(:root) do
|
1459
1577
|
scope(parent_resource.path) do
|
1460
1578
|
super(options)
|
@@ -1477,6 +1595,13 @@ module ActionDispatch
|
|
1477
1595
|
return true
|
1478
1596
|
end
|
1479
1597
|
|
1598
|
+
if options.delete(:shallow)
|
1599
|
+
shallow do
|
1600
|
+
send(method, resources.pop, options, &block)
|
1601
|
+
end
|
1602
|
+
return true
|
1603
|
+
end
|
1604
|
+
|
1480
1605
|
if resource_scope?
|
1481
1606
|
nested { send(method, resources.pop, options, &block) }
|
1482
1607
|
return true
|
@@ -1514,41 +1639,47 @@ module ActionDispatch
|
|
1514
1639
|
end
|
1515
1640
|
|
1516
1641
|
def resource_scope? #:nodoc:
|
1517
|
-
|
1642
|
+
@scope.resource_scope?
|
1518
1643
|
end
|
1519
1644
|
|
1520
1645
|
def resource_method_scope? #:nodoc:
|
1521
|
-
|
1646
|
+
@scope.resource_method_scope?
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
def nested_scope? #:nodoc:
|
1650
|
+
@scope.nested?
|
1522
1651
|
end
|
1523
1652
|
|
1524
1653
|
def with_exclusive_scope
|
1525
1654
|
begin
|
1526
|
-
|
1527
|
-
@scope[:as], @scope[:path] = nil, nil
|
1655
|
+
@scope = @scope.new(:as => nil, :path => nil)
|
1528
1656
|
|
1529
1657
|
with_scope_level(:exclusive) do
|
1530
1658
|
yield
|
1531
1659
|
end
|
1532
1660
|
ensure
|
1533
|
-
@scope
|
1661
|
+
@scope = @scope.parent
|
1534
1662
|
end
|
1535
1663
|
end
|
1536
1664
|
|
1537
|
-
def with_scope_level(kind
|
1538
|
-
|
1539
|
-
old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
|
1665
|
+
def with_scope_level(kind)
|
1666
|
+
@scope = @scope.new_level(kind)
|
1540
1667
|
yield
|
1541
1668
|
ensure
|
1542
|
-
@scope
|
1543
|
-
@scope[:scope_level_resource] = old_resource
|
1669
|
+
@scope = @scope.parent
|
1544
1670
|
end
|
1545
1671
|
|
1546
1672
|
def resource_scope(kind, resource) #:nodoc:
|
1547
|
-
|
1548
|
-
|
1549
|
-
|
1550
|
-
|
1673
|
+
resource.shallow = @scope[:shallow]
|
1674
|
+
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
+
@nesting.push(resource)
|
1676
|
+
|
1677
|
+
with_scope_level(kind) do
|
1678
|
+
scope(parent_resource.resource_scope) { yield }
|
1551
1679
|
end
|
1680
|
+
ensure
|
1681
|
+
@nesting.pop
|
1682
|
+
@scope = @scope.parent
|
1552
1683
|
end
|
1553
1684
|
|
1554
1685
|
def nested_options #:nodoc:
|
@@ -1560,6 +1691,14 @@ module ActionDispatch
|
|
1560
1691
|
options
|
1561
1692
|
end
|
1562
1693
|
|
1694
|
+
def nesting_depth #:nodoc:
|
1695
|
+
@nesting.size
|
1696
|
+
end
|
1697
|
+
|
1698
|
+
def shallow_nesting_depth #:nodoc:
|
1699
|
+
@nesting.select(&:shallow?).size
|
1700
|
+
end
|
1701
|
+
|
1563
1702
|
def param_constraint? #:nodoc:
|
1564
1703
|
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
1565
1704
|
end
|
@@ -1568,22 +1707,25 @@ module ActionDispatch
|
|
1568
1707
|
@scope[:constraints][parent_resource.param]
|
1569
1708
|
end
|
1570
1709
|
|
1571
|
-
def canonical_action?(action
|
1572
|
-
|
1710
|
+
def canonical_action?(action) #:nodoc:
|
1711
|
+
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1573
1712
|
end
|
1574
1713
|
|
1575
|
-
def
|
1576
|
-
|
1714
|
+
def shallow_scope(path, options = {}) #:nodoc:
|
1715
|
+
scope = { :as => @scope[:shallow_prefix],
|
1716
|
+
:path => @scope[:shallow_path] }
|
1717
|
+
@scope = @scope.new scope
|
1718
|
+
|
1719
|
+
scope(path, options) { yield }
|
1720
|
+
ensure
|
1721
|
+
@scope = @scope.parent
|
1577
1722
|
end
|
1578
1723
|
|
1579
1724
|
def path_for_action(action, path) #:nodoc:
|
1580
|
-
|
1581
|
-
|
1582
|
-
|
1583
|
-
if canonical_action?(action, path.blank?)
|
1584
|
-
prefix.to_s
|
1725
|
+
if path.blank? && canonical_action?(action)
|
1726
|
+
@scope[:path].to_s
|
1585
1727
|
else
|
1586
|
-
"#{
|
1728
|
+
"#{@scope[:path]}/#{action_path(action, path)}"
|
1587
1729
|
end
|
1588
1730
|
end
|
1589
1731
|
|
@@ -1594,15 +1736,18 @@ module ActionDispatch
|
|
1594
1736
|
|
1595
1737
|
def prefix_name_for_action(as, action) #:nodoc:
|
1596
1738
|
if as
|
1597
|
-
as
|
1598
|
-
elsif !canonical_action?(action
|
1599
|
-
action
|
1739
|
+
prefix = as
|
1740
|
+
elsif !canonical_action?(action)
|
1741
|
+
prefix = action
|
1742
|
+
end
|
1743
|
+
|
1744
|
+
if prefix && prefix != '/' && !prefix.empty?
|
1745
|
+
Mapper.normalize_name prefix.to_s.tr('-', '_')
|
1600
1746
|
end
|
1601
1747
|
end
|
1602
1748
|
|
1603
1749
|
def name_for_action(as, action) #:nodoc:
|
1604
1750
|
prefix = prefix_name_for_action(as, action)
|
1605
|
-
prefix = Mapper.normalize_name(prefix) if prefix
|
1606
1751
|
name_prefix = @scope[:as]
|
1607
1752
|
|
1608
1753
|
if parent_resource
|
@@ -1612,27 +1757,14 @@ module ActionDispatch
|
|
1612
1757
|
member_name = parent_resource.member_name
|
1613
1758
|
end
|
1614
1759
|
|
1615
|
-
name =
|
1616
|
-
when :nested
|
1617
|
-
[name_prefix, prefix]
|
1618
|
-
when :collection
|
1619
|
-
[prefix, name_prefix, collection_name]
|
1620
|
-
when :new
|
1621
|
-
[prefix, :new, name_prefix, member_name]
|
1622
|
-
when :member
|
1623
|
-
[prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name]
|
1624
|
-
when :root
|
1625
|
-
[name_prefix, collection_name, prefix]
|
1626
|
-
else
|
1627
|
-
[name_prefix, member_name, prefix]
|
1628
|
-
end
|
1760
|
+
name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1629
1761
|
|
1630
|
-
if candidate = name.
|
1762
|
+
if candidate = name.compact.join("_").presence
|
1631
1763
|
# If a name was not explicitly given, we check if it is valid
|
1632
1764
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1633
1765
|
# forward so the underlying router engine treats it and raises an exception.
|
1634
1766
|
if as.nil?
|
1635
|
-
candidate unless
|
1767
|
+
candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
|
1636
1768
|
else
|
1637
1769
|
candidate
|
1638
1770
|
end
|
@@ -1757,10 +1889,85 @@ module ActionDispatch
|
|
1757
1889
|
end
|
1758
1890
|
end
|
1759
1891
|
|
1892
|
+
class Scope # :nodoc:
|
1893
|
+
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
|
+
:controller, :action, :path_names, :constraints,
|
1895
|
+
:shallow, :blocks, :defaults, :options]
|
1896
|
+
|
1897
|
+
RESOURCE_SCOPES = [:resource, :resources]
|
1898
|
+
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
|
+
|
1900
|
+
attr_reader :parent, :scope_level
|
1901
|
+
|
1902
|
+
def initialize(hash, parent = {}, scope_level = nil)
|
1903
|
+
@hash = hash
|
1904
|
+
@parent = parent
|
1905
|
+
@scope_level = scope_level
|
1906
|
+
end
|
1907
|
+
|
1908
|
+
def nested?
|
1909
|
+
scope_level == :nested
|
1910
|
+
end
|
1911
|
+
|
1912
|
+
def resources?
|
1913
|
+
scope_level == :resources
|
1914
|
+
end
|
1915
|
+
|
1916
|
+
def resource_method_scope?
|
1917
|
+
RESOURCE_METHOD_SCOPES.include? scope_level
|
1918
|
+
end
|
1919
|
+
|
1920
|
+
def action_name(name_prefix, prefix, collection_name, member_name)
|
1921
|
+
case scope_level
|
1922
|
+
when :nested
|
1923
|
+
[name_prefix, prefix]
|
1924
|
+
when :collection
|
1925
|
+
[prefix, name_prefix, collection_name]
|
1926
|
+
when :new
|
1927
|
+
[prefix, :new, name_prefix, member_name]
|
1928
|
+
when :member
|
1929
|
+
[prefix, name_prefix, member_name]
|
1930
|
+
when :root
|
1931
|
+
[name_prefix, collection_name, prefix]
|
1932
|
+
else
|
1933
|
+
[name_prefix, member_name, prefix]
|
1934
|
+
end
|
1935
|
+
end
|
1936
|
+
|
1937
|
+
def resource_scope?
|
1938
|
+
RESOURCE_SCOPES.include? scope_level
|
1939
|
+
end
|
1940
|
+
|
1941
|
+
def options
|
1942
|
+
OPTIONS
|
1943
|
+
end
|
1944
|
+
|
1945
|
+
def new(hash)
|
1946
|
+
self.class.new hash, self, scope_level
|
1947
|
+
end
|
1948
|
+
|
1949
|
+
def new_level(level)
|
1950
|
+
self.class.new(self, self, level)
|
1951
|
+
end
|
1952
|
+
|
1953
|
+
def fetch(key, &block)
|
1954
|
+
@hash.fetch(key, &block)
|
1955
|
+
end
|
1956
|
+
|
1957
|
+
def [](key)
|
1958
|
+
@hash.fetch(key) { @parent[key] }
|
1959
|
+
end
|
1960
|
+
|
1961
|
+
def []=(k,v)
|
1962
|
+
@hash[k] = v
|
1963
|
+
end
|
1964
|
+
end
|
1965
|
+
|
1760
1966
|
def initialize(set) #:nodoc:
|
1761
1967
|
@set = set
|
1762
|
-
@scope = { :path_names => @set.resources_path_names }
|
1968
|
+
@scope = Scope.new({ :path_names => @set.resources_path_names })
|
1763
1969
|
@concerns = {}
|
1970
|
+
@nesting = []
|
1764
1971
|
end
|
1765
1972
|
|
1766
1973
|
include Base
|