actionpack 3.2.22.5 → 5.2.4
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 +279 -603
- data/MIT-LICENSE +1 -1
- data/README.rdoc +13 -297
- data/lib/abstract_controller/asset_paths.rb +4 -2
- data/lib/abstract_controller/base.rb +82 -52
- data/lib/abstract_controller/caching/fragments.rb +166 -0
- data/lib/abstract_controller/caching.rb +66 -0
- data/lib/abstract_controller/callbacks.rb +117 -103
- data/lib/abstract_controller/collector.rb +18 -7
- data/lib/abstract_controller/error.rb +6 -0
- data/lib/abstract_controller/helpers.rb +65 -38
- data/lib/abstract_controller/logger.rb +3 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +5 -3
- data/lib/abstract_controller/rendering.rb +77 -129
- data/lib/abstract_controller/translation.rb +21 -3
- data/lib/abstract_controller/url_for.rb +9 -7
- data/lib/abstract_controller.rb +12 -13
- data/lib/action_controller/api/api_rendering.rb +16 -0
- data/lib/action_controller/api.rb +149 -0
- data/lib/action_controller/base.rb +81 -40
- data/lib/action_controller/caching.rb +22 -62
- data/lib/action_controller/form_builder.rb +50 -0
- data/lib/action_controller/log_subscriber.rb +30 -18
- data/lib/action_controller/metal/basic_implicit_render.rb +13 -0
- data/lib/action_controller/metal/conditional_get.rb +190 -47
- data/lib/action_controller/metal/content_security_policy.rb +52 -0
- data/lib/action_controller/metal/cookies.rb +3 -3
- data/lib/action_controller/metal/data_streaming.rb +40 -65
- data/lib/action_controller/metal/etag_with_flash.rb +18 -0
- data/lib/action_controller/metal/etag_with_template_digest.rb +57 -0
- data/lib/action_controller/metal/exceptions.rb +19 -12
- data/lib/action_controller/metal/flash.rb +42 -9
- data/lib/action_controller/metal/force_ssl.rb +79 -19
- data/lib/action_controller/metal/head.rb +35 -10
- data/lib/action_controller/metal/helpers.rb +31 -21
- data/lib/action_controller/metal/http_authentication.rb +182 -134
- data/lib/action_controller/metal/implicit_render.rb +62 -8
- data/lib/action_controller/metal/instrumentation.rb +28 -26
- data/lib/action_controller/metal/live.rb +312 -0
- data/lib/action_controller/metal/mime_responds.rb +159 -163
- data/lib/action_controller/metal/parameter_encoding.rb +51 -0
- data/lib/action_controller/metal/params_wrapper.rb +146 -93
- data/lib/action_controller/metal/redirecting.rb +80 -56
- data/lib/action_controller/metal/renderers.rb +119 -47
- data/lib/action_controller/metal/rendering.rb +89 -32
- data/lib/action_controller/metal/request_forgery_protection.rb +373 -41
- data/lib/action_controller/metal/rescue.rb +9 -16
- data/lib/action_controller/metal/streaming.rb +39 -45
- data/lib/action_controller/metal/strong_parameters.rb +1086 -0
- data/lib/action_controller/metal/testing.rb +8 -29
- data/lib/action_controller/metal/url_for.rb +43 -32
- data/lib/action_controller/metal.rb +112 -106
- data/lib/action_controller/railtie.rb +56 -18
- data/lib/action_controller/railties/helpers.rb +24 -0
- data/lib/action_controller/renderer.rb +117 -0
- data/lib/action_controller/template_assertions.rb +11 -0
- data/lib/action_controller/test_case.rb +402 -347
- data/lib/action_controller.rb +31 -30
- data/lib/action_dispatch/http/cache.rb +133 -34
- data/lib/action_dispatch/http/content_security_policy.rb +272 -0
- data/lib/action_dispatch/http/filter_parameters.rb +40 -24
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +117 -16
- data/lib/action_dispatch/http/mime_negotiation.rb +98 -33
- data/lib/action_dispatch/http/mime_type.rb +198 -146
- data/lib/action_dispatch/http/mime_types.rb +22 -7
- data/lib/action_dispatch/http/parameter_filter.rb +61 -49
- data/lib/action_dispatch/http/parameters.rb +94 -51
- data/lib/action_dispatch/http/rack_cache.rb +4 -3
- data/lib/action_dispatch/http/request.rb +262 -117
- data/lib/action_dispatch/http/response.rb +400 -86
- data/lib/action_dispatch/http/upload.rb +66 -29
- data/lib/action_dispatch/http/url.rb +232 -60
- data/lib/action_dispatch/journey/formatter.rb +189 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +164 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +41 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +158 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +78 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +49 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +120 -0
- data/lib/action_dispatch/journey/nodes/node.rb +140 -0
- data/lib/action_dispatch/journey/parser.rb +199 -0
- data/lib/action_dispatch/journey/parser.y +50 -0
- data/lib/action_dispatch/journey/parser_extras.rb +31 -0
- data/lib/action_dispatch/journey/path/pattern.rb +199 -0
- data/lib/action_dispatch/journey/route.rb +203 -0
- data/lib/action_dispatch/journey/router/utils.rb +102 -0
- data/lib/action_dispatch/journey/router.rb +156 -0
- data/lib/action_dispatch/journey/routes.rb +82 -0
- data/lib/action_dispatch/journey/scanner.rb +64 -0
- data/lib/action_dispatch/journey/visitors.rb +268 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +30 -0
- data/lib/action_dispatch/journey/visualizer/fsm.js +134 -0
- data/lib/action_dispatch/journey/visualizer/index.html.erb +52 -0
- data/lib/action_dispatch/journey.rb +7 -0
- data/lib/action_dispatch/middleware/callbacks.rb +17 -13
- data/lib/action_dispatch/middleware/cookies.rb +494 -162
- data/lib/action_dispatch/middleware/debug_exceptions.rb +176 -53
- data/lib/action_dispatch/middleware/debug_locks.rb +124 -0
- data/lib/action_dispatch/middleware/exception_wrapper.rb +103 -38
- data/lib/action_dispatch/middleware/executor.rb +21 -0
- data/lib/action_dispatch/middleware/flash.rb +128 -91
- data/lib/action_dispatch/middleware/public_exceptions.rb +43 -16
- data/lib/action_dispatch/middleware/reloader.rb +6 -83
- data/lib/action_dispatch/middleware/remote_ip.rb +151 -49
- data/lib/action_dispatch/middleware/request_id.rb +19 -15
- data/lib/action_dispatch/middleware/session/abstract_store.rb +38 -34
- data/lib/action_dispatch/middleware/session/cache_store.rb +14 -9
- data/lib/action_dispatch/middleware/session/cookie_store.rb +94 -44
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +15 -4
- data/lib/action_dispatch/middleware/show_exceptions.rb +36 -61
- data/lib/action_dispatch/middleware/ssl.rb +150 -0
- data/lib/action_dispatch/middleware/stack.rb +33 -41
- data/lib/action_dispatch/middleware/static.rb +92 -48
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +22 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +27 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- 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.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +9 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +21 -0
- data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +13 -0
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +134 -5
- 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.html.erb +32 -0
- 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.html.erb +6 -0
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +3 -0
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +200 -0
- data/lib/action_dispatch/railtie.rb +29 -8
- data/lib/action_dispatch/request/session.rb +234 -0
- data/lib/action_dispatch/request/utils.rb +78 -0
- data/lib/action_dispatch/routing/endpoint.rb +17 -0
- data/lib/action_dispatch/routing/inspector.rb +225 -0
- data/lib/action_dispatch/routing/mapper.rb +1329 -582
- data/lib/action_dispatch/routing/polymorphic_routes.rb +237 -94
- data/lib/action_dispatch/routing/redirection.rb +120 -50
- data/lib/action_dispatch/routing/route_set.rb +545 -322
- data/lib/action_dispatch/routing/routes_proxy.rb +37 -7
- data/lib/action_dispatch/routing/url_for.rb +103 -34
- data/lib/action_dispatch/routing.rb +66 -99
- data/lib/action_dispatch/system_test_case.rb +147 -0
- data/lib/action_dispatch/system_testing/browser.rb +49 -0
- data/lib/action_dispatch/system_testing/driver.rb +59 -0
- data/lib/action_dispatch/system_testing/server.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +96 -0
- data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +31 -0
- data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +26 -0
- data/lib/action_dispatch/testing/assertion_response.rb +47 -0
- data/lib/action_dispatch/testing/assertions/response.rb +53 -42
- data/lib/action_dispatch/testing/assertions/routing.rb +79 -74
- data/lib/action_dispatch/testing/assertions.rb +15 -9
- data/lib/action_dispatch/testing/integration.rb +361 -207
- data/lib/action_dispatch/testing/request_encoder.rb +55 -0
- data/lib/action_dispatch/testing/test_process.rb +28 -19
- data/lib/action_dispatch/testing/test_request.rb +30 -33
- data/lib/action_dispatch/testing/test_response.rb +35 -11
- data/lib/action_dispatch.rb +42 -32
- data/lib/action_pack/gem_version.rb +17 -0
- data/lib/action_pack/version.rb +7 -7
- data/lib/action_pack.rb +4 -2
- metadata +116 -175
- data/lib/abstract_controller/layouts.rb +0 -423
- data/lib/abstract_controller/view_paths.rb +0 -96
- data/lib/action_controller/caching/actions.rb +0 -185
- data/lib/action_controller/caching/fragments.rb +0 -127
- data/lib/action_controller/caching/pages.rb +0 -187
- data/lib/action_controller/caching/sweeping.rb +0 -97
- data/lib/action_controller/deprecated/integration_test.rb +0 -2
- data/lib/action_controller/deprecated/performance_test.rb +0 -1
- data/lib/action_controller/deprecated.rb +0 -3
- data/lib/action_controller/metal/compatibility.rb +0 -65
- data/lib/action_controller/metal/hide_actions.rb +0 -41
- data/lib/action_controller/metal/rack_delegation.rb +0 -26
- data/lib/action_controller/metal/responder.rb +0 -286
- data/lib/action_controller/metal/session_management.rb +0 -14
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/railties/paths.rb +0 -25
- data/lib/action_controller/record_identifier.rb +0 -85
- data/lib/action_controller/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_controller/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_controller/vendor/html-scanner/html/sanitizer.rb +0 -177
- data/lib/action_controller/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_controller/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_controller/vendor/html-scanner.rb +0 -20
- data/lib/action_dispatch/middleware/best_standards_support.rb +0 -30
- data/lib/action_dispatch/middleware/body_proxy.rb +0 -30
- data/lib/action_dispatch/middleware/head.rb +0 -18
- data/lib/action_dispatch/middleware/params_parser.rb +0 -75
- data/lib/action_dispatch/middleware/rescue.rb +0 -26
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +0 -31
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +0 -26
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +0 -10
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +0 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +0 -15
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +0 -17
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +0 -2
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -37
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -435
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -138
- data/lib/action_dispatch/testing/performance_test.rb +0 -10
- data/lib/action_view/asset_paths.rb +0 -142
- data/lib/action_view/base.rb +0 -220
- data/lib/action_view/buffers.rb +0 -43
- data/lib/action_view/context.rb +0 -36
- data/lib/action_view/flows.rb +0 -79
- data/lib/action_view/helpers/active_model_helper.rb +0 -50
- data/lib/action_view/helpers/asset_paths.rb +0 -7
- data/lib/action_view/helpers/asset_tag_helper.rb +0 -457
- data/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +0 -146
- data/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +0 -93
- data/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +0 -193
- data/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +0 -148
- data/lib/action_view/helpers/atom_feed_helper.rb +0 -200
- data/lib/action_view/helpers/cache_helper.rb +0 -64
- data/lib/action_view/helpers/capture_helper.rb +0 -203
- data/lib/action_view/helpers/controller_helper.rb +0 -25
- data/lib/action_view/helpers/csrf_helper.rb +0 -32
- data/lib/action_view/helpers/date_helper.rb +0 -1062
- data/lib/action_view/helpers/debug_helper.rb +0 -40
- data/lib/action_view/helpers/form_helper.rb +0 -1486
- data/lib/action_view/helpers/form_options_helper.rb +0 -658
- data/lib/action_view/helpers/form_tag_helper.rb +0 -685
- data/lib/action_view/helpers/javascript_helper.rb +0 -110
- data/lib/action_view/helpers/number_helper.rb +0 -622
- data/lib/action_view/helpers/output_safety_helper.rb +0 -38
- data/lib/action_view/helpers/record_tag_helper.rb +0 -111
- data/lib/action_view/helpers/rendering_helper.rb +0 -92
- data/lib/action_view/helpers/sanitize_helper.rb +0 -259
- data/lib/action_view/helpers/tag_helper.rb +0 -167
- data/lib/action_view/helpers/text_helper.rb +0 -426
- data/lib/action_view/helpers/translation_helper.rb +0 -91
- data/lib/action_view/helpers/url_helper.rb +0 -693
- data/lib/action_view/helpers.rb +0 -60
- data/lib/action_view/locale/en.yml +0 -160
- data/lib/action_view/log_subscriber.rb +0 -28
- data/lib/action_view/lookup_context.rb +0 -258
- data/lib/action_view/path_set.rb +0 -101
- data/lib/action_view/railtie.rb +0 -55
- data/lib/action_view/renderer/abstract_renderer.rb +0 -41
- data/lib/action_view/renderer/partial_renderer.rb +0 -415
- data/lib/action_view/renderer/renderer.rb +0 -61
- data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
- data/lib/action_view/renderer/template_renderer.rb +0 -95
- data/lib/action_view/template/error.rb +0 -128
- data/lib/action_view/template/handlers/builder.rb +0 -26
- data/lib/action_view/template/handlers/erb.rb +0 -125
- data/lib/action_view/template/handlers.rb +0 -50
- data/lib/action_view/template/resolver.rb +0 -298
- data/lib/action_view/template/text.rb +0 -30
- data/lib/action_view/template.rb +0 -337
- data/lib/action_view/test_case.rb +0 -246
- data/lib/action_view/testing/resolvers.rb +0 -49
- data/lib/action_view.rb +0 -84
- data/lib/sprockets/assets.rake +0 -99
- data/lib/sprockets/bootstrap.rb +0 -37
- data/lib/sprockets/compressors.rb +0 -83
- data/lib/sprockets/helpers/isolated_helper.rb +0 -13
- data/lib/sprockets/helpers/rails_helper.rb +0 -182
- data/lib/sprockets/helpers.rb +0 -6
- data/lib/sprockets/railtie.rb +0 -62
- data/lib/sprockets/static_compiler.rb +0 -56
@@ -1,293 +1,464 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/hash/slice"
|
4
|
+
require "active_support/core_ext/enumerable"
|
5
|
+
require "active_support/core_ext/array/extract_options"
|
6
|
+
require "active_support/core_ext/regexp"
|
7
|
+
require "action_dispatch/routing/redirection"
|
8
|
+
require "action_dispatch/routing/endpoint"
|
7
9
|
|
8
10
|
module ActionDispatch
|
9
11
|
module Routing
|
10
12
|
class Mapper
|
11
|
-
|
12
|
-
def self.new(app, constraints, request = Rack::Request)
|
13
|
-
if constraints.any?
|
14
|
-
super(app, constraints, request)
|
15
|
-
else
|
16
|
-
app
|
17
|
-
end
|
18
|
-
end
|
13
|
+
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
19
14
|
|
15
|
+
class Constraints < Routing::Endpoint #:nodoc:
|
20
16
|
attr_reader :app, :constraints
|
21
17
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
18
|
+
SERVE = ->(app, req) { app.serve req }
|
19
|
+
CALL = ->(app, req) { app.call req.env }
|
25
20
|
|
26
|
-
def
|
27
|
-
|
21
|
+
def initialize(app, constraints, strategy)
|
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
|
28
30
|
|
29
|
-
@
|
30
|
-
if constraint.respond_to?(:matches?) && !constraint.matches?(req)
|
31
|
-
return false
|
32
|
-
elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
|
33
|
-
return false
|
34
|
-
end
|
35
|
-
}
|
31
|
+
@strategy = strategy
|
36
32
|
|
37
|
-
|
38
|
-
ensure
|
39
|
-
req.reset_parameters
|
33
|
+
@app, @constraints, = app, constraints
|
40
34
|
end
|
41
35
|
|
42
|
-
def
|
43
|
-
|
36
|
+
def dispatcher?; @strategy == SERVE; end
|
37
|
+
|
38
|
+
def matches?(req)
|
39
|
+
@constraints.all? do |constraint|
|
40
|
+
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
|
41
|
+
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def serve(req)
|
46
|
+
return [ 404, { "X-Cascade" => "pass" }, [] ] unless matches?(req)
|
47
|
+
|
48
|
+
@strategy.call @app, req
|
44
49
|
end
|
45
50
|
|
46
51
|
private
|
47
52
|
def constraint_args(constraint, request)
|
48
|
-
constraint.arity == 1 ? [request] : [request.
|
53
|
+
constraint.arity == 1 ? [request] : [request.path_parameters, request]
|
49
54
|
end
|
50
55
|
end
|
51
56
|
|
52
57
|
class Mapping #:nodoc:
|
53
|
-
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
|
54
58
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
55
|
-
|
59
|
+
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
60
|
+
|
61
|
+
attr_reader :requirements, :defaults
|
62
|
+
attr_reader :to, :default_controller, :default_action
|
63
|
+
attr_reader :required_defaults, :ast
|
56
64
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
65
|
+
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
66
|
+
options = scope[:options].merge(options) if scope[:options]
|
67
|
+
|
68
|
+
defaults = (scope[:defaults] || {}).dup
|
69
|
+
scope_constraints = scope[:constraints] || {}
|
70
|
+
|
71
|
+
new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
|
62
72
|
end
|
63
73
|
|
64
|
-
def
|
65
|
-
|
74
|
+
def self.check_via(via)
|
75
|
+
if via.empty?
|
76
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
77
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
78
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
79
|
+
" Instead of: match \"controller#action\"\n" \
|
80
|
+
" Do: get \"controller#action\""
|
81
|
+
raise ArgumentError, msg
|
82
|
+
end
|
83
|
+
via
|
66
84
|
end
|
67
85
|
|
68
|
-
|
86
|
+
def self.normalize_path(path, format)
|
87
|
+
path = Mapper.normalize_path(path)
|
69
88
|
|
70
|
-
|
71
|
-
|
89
|
+
if format == true
|
90
|
+
"#{path}.:format"
|
91
|
+
elsif optional_format?(path, format)
|
92
|
+
"#{path}(.:format)"
|
93
|
+
else
|
94
|
+
path
|
95
|
+
end
|
96
|
+
end
|
72
97
|
|
73
|
-
|
74
|
-
|
75
|
-
|
98
|
+
def self.optional_format?(path, format)
|
99
|
+
format != false && path !~ OPTIONAL_FORMAT_REGEX
|
100
|
+
end
|
76
101
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
102
|
+
def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
|
103
|
+
@defaults = defaults
|
104
|
+
@set = set
|
105
|
+
|
106
|
+
@to = to
|
107
|
+
@default_controller = controller
|
108
|
+
@default_action = default_action
|
109
|
+
@ast = ast
|
110
|
+
@anchor = anchor
|
111
|
+
@via = via
|
112
|
+
@internal = options.delete(:internal)
|
113
|
+
|
114
|
+
path_params = ast.find_all(&:symbol?).map(&:to_sym)
|
115
|
+
|
116
|
+
options = add_wildcard_options(options, formatted, ast)
|
117
|
+
|
118
|
+
options = normalize_options!(options, path_params, modyoule)
|
119
|
+
|
120
|
+
split_options = constraints(options, path_params)
|
121
|
+
|
122
|
+
constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
|
123
|
+
|
124
|
+
if options_constraints.is_a?(Hash)
|
125
|
+
@defaults = Hash[options_constraints.find_all { |key, default|
|
126
|
+
URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
127
|
+
}].merge @defaults
|
128
|
+
@blocks = blocks
|
129
|
+
constraints.merge! options_constraints
|
130
|
+
else
|
131
|
+
@blocks = blocks(options_constraints)
|
84
132
|
end
|
85
133
|
|
86
|
-
|
87
|
-
|
88
|
-
|
134
|
+
requirements, conditions = split_constraints path_params, constraints
|
135
|
+
verify_regexp_requirements requirements.map(&:last).grep(Regexp)
|
136
|
+
|
137
|
+
formats = normalize_format(formatted)
|
138
|
+
|
139
|
+
@requirements = formats[:requirements].merge Hash[requirements]
|
140
|
+
@conditions = Hash[conditions]
|
141
|
+
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
142
|
+
|
143
|
+
if path_params.include?(:action) && !@requirements.key?(:action)
|
144
|
+
@defaults[:action] ||= "index"
|
89
145
|
end
|
90
146
|
|
91
|
-
|
92
|
-
|
93
|
-
path = Mapper.normalize_path(path)
|
147
|
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
148
|
+
end
|
94
149
|
|
95
|
-
|
96
|
-
|
150
|
+
def make_route(name, precedence)
|
151
|
+
route = Journey::Route.new(name,
|
152
|
+
application,
|
153
|
+
path,
|
154
|
+
conditions,
|
155
|
+
required_defaults,
|
156
|
+
defaults,
|
157
|
+
request_method,
|
158
|
+
precedence,
|
159
|
+
@internal)
|
160
|
+
|
161
|
+
route
|
162
|
+
end
|
97
163
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
164
|
+
def application
|
165
|
+
app(@blocks)
|
166
|
+
end
|
167
|
+
|
168
|
+
def path
|
169
|
+
build_path @ast, requirements, @anchor
|
170
|
+
end
|
171
|
+
|
172
|
+
def conditions
|
173
|
+
build_conditions @conditions, @set.request_class
|
174
|
+
end
|
175
|
+
|
176
|
+
def build_conditions(current_conditions, request_class)
|
177
|
+
conditions = current_conditions.dup
|
178
|
+
|
179
|
+
conditions.keep_if do |k, _|
|
180
|
+
request_class.public_method_defined?(k)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
private :build_conditions
|
184
|
+
|
185
|
+
def request_method
|
186
|
+
@via.map { |x| Journey::Route.verb_matcher(x) }
|
187
|
+
end
|
188
|
+
private :request_method
|
189
|
+
|
190
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
191
|
+
|
192
|
+
def build_path(ast, requirements, anchor)
|
193
|
+
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
|
194
|
+
|
195
|
+
# Find all the symbol nodes that are adjacent to literal nodes and alter
|
196
|
+
# the regexp so that Journey will partition them into custom routes.
|
197
|
+
ast.find_all { |node|
|
198
|
+
next unless node.cat?
|
199
|
+
|
200
|
+
if node.left.literal? && node.right.symbol?
|
201
|
+
symbol = node.right
|
202
|
+
elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
|
203
|
+
symbol = node.right.left
|
204
|
+
elsif node.left.symbol? && node.right.literal?
|
205
|
+
symbol = node.left
|
206
|
+
elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
|
207
|
+
symbol = node.left
|
208
|
+
else
|
209
|
+
next
|
103
210
|
end
|
104
211
|
|
105
|
-
|
106
|
-
|
107
|
-
if path.match(WILDCARD_PATH) && @options[:format] != false
|
108
|
-
@options[$1.to_sym] ||= /.+?/
|
212
|
+
if symbol
|
213
|
+
symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
|
109
214
|
end
|
215
|
+
}
|
216
|
+
|
217
|
+
pattern
|
218
|
+
end
|
219
|
+
private :build_path
|
110
220
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
221
|
+
private
|
222
|
+
def add_wildcard_options(options, formatted, path_ast)
|
223
|
+
# Add a constraint for wildcard route to make it non-greedy and match the
|
224
|
+
# optional format part of the route by default.
|
225
|
+
if formatted != false
|
226
|
+
path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
|
227
|
+
hash[node.name.to_sym] ||= /.+?/
|
228
|
+
}.merge options
|
118
229
|
else
|
119
|
-
|
230
|
+
options
|
120
231
|
end
|
121
232
|
end
|
122
233
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
blocks,
|
127
|
-
@set.request_class
|
128
|
-
)
|
129
|
-
end
|
234
|
+
def normalize_options!(options, path_params, modyoule)
|
235
|
+
if path_params.include?(:controller)
|
236
|
+
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
130
237
|
|
131
|
-
|
132
|
-
|
133
|
-
|
238
|
+
# Add a default constraint for :controller path segments that matches namespaced
|
239
|
+
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
240
|
+
# GET /admin/products/show/1
|
241
|
+
# => { controller: 'admin/products', action: 'show', id: '1' }
|
242
|
+
options[:controller] ||= /.+?/
|
243
|
+
end
|
134
244
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
245
|
+
if to.respond_to?(:action) || to.respond_to?(:call)
|
246
|
+
options
|
247
|
+
else
|
248
|
+
to_endpoint = split_to to
|
249
|
+
controller = to_endpoint[0] || default_controller
|
250
|
+
action = to_endpoint[1] || default_action
|
251
|
+
|
252
|
+
controller = add_controller_module(controller, modyoule)
|
253
|
+
|
254
|
+
options.merge! check_controller_and_action(path_params, controller, action)
|
139
255
|
end
|
140
256
|
end
|
141
257
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
145
|
-
@options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
|
258
|
+
def split_constraints(path_params, constraints)
|
259
|
+
constraints.partition do |key, requirement|
|
260
|
+
path_params.include?(key) || key == :controller
|
146
261
|
end
|
147
262
|
end
|
148
263
|
|
149
|
-
def
|
150
|
-
|
151
|
-
|
264
|
+
def normalize_format(formatted)
|
265
|
+
case formatted
|
266
|
+
when true
|
267
|
+
{ requirements: { format: /.+/ },
|
268
|
+
defaults: {} }
|
269
|
+
when Regexp
|
270
|
+
{ requirements: { format: formatted },
|
271
|
+
defaults: { format: nil } }
|
272
|
+
when String
|
273
|
+
{ requirements: { format: Regexp.compile(formatted) },
|
274
|
+
defaults: { format: formatted } }
|
152
275
|
else
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
action = to.to_s
|
157
|
-
end
|
158
|
-
|
159
|
-
controller ||= default_controller
|
160
|
-
action ||= default_action
|
276
|
+
{ requirements: {}, defaults: {} }
|
277
|
+
end
|
278
|
+
end
|
161
279
|
|
162
|
-
|
163
|
-
|
280
|
+
def verify_regexp_requirements(requirements)
|
281
|
+
requirements.each do |requirement|
|
282
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
283
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
164
284
|
end
|
165
285
|
|
166
|
-
if
|
167
|
-
raise ArgumentError, "
|
286
|
+
if requirement.multiline?
|
287
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
168
288
|
end
|
289
|
+
end
|
290
|
+
end
|
169
291
|
|
170
|
-
|
171
|
-
|
292
|
+
def normalize_defaults(options)
|
293
|
+
Hash[options.reject { |_, default| Regexp === default }]
|
294
|
+
end
|
172
295
|
|
173
|
-
|
174
|
-
|
175
|
-
|
296
|
+
def app(blocks)
|
297
|
+
if to.respond_to?(:action)
|
298
|
+
Routing::RouteSet::StaticDispatcher.new to
|
299
|
+
elsif to.respond_to?(:call)
|
300
|
+
Constraints.new(to, blocks, Constraints::CALL)
|
301
|
+
elsif blocks.any?
|
302
|
+
Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
|
303
|
+
else
|
304
|
+
dispatcher(defaults.key?(:controller))
|
305
|
+
end
|
306
|
+
end
|
176
307
|
|
177
|
-
|
178
|
-
|
179
|
-
|
308
|
+
def check_controller_and_action(path_params, controller, action)
|
309
|
+
hash = check_part(:controller, controller, path_params, {}) do |part|
|
310
|
+
translate_controller(part) {
|
311
|
+
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup
|
312
|
+
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
180
313
|
|
181
|
-
|
182
|
-
|
183
|
-
hash[:action] = action unless action.blank?
|
184
|
-
hash
|
314
|
+
raise ArgumentError, message
|
315
|
+
}
|
185
316
|
end
|
317
|
+
|
318
|
+
check_part(:action, action, path_params, hash) { |part|
|
319
|
+
part.is_a?(Regexp) ? part : part.to_s
|
320
|
+
}
|
186
321
|
end
|
187
322
|
|
188
|
-
def
|
189
|
-
|
190
|
-
|
191
|
-
[constraints]
|
323
|
+
def check_part(name, part, path_params, hash)
|
324
|
+
if part
|
325
|
+
hash[name] = yield(part)
|
192
326
|
else
|
193
|
-
|
327
|
+
unless path_params.include?(name)
|
328
|
+
message = "Missing :#{name} key on routes definition, please check your routes."
|
329
|
+
raise ArgumentError, message
|
330
|
+
end
|
194
331
|
end
|
332
|
+
hash
|
195
333
|
end
|
196
334
|
|
197
|
-
def
|
198
|
-
|
335
|
+
def split_to(to)
|
336
|
+
if to =~ /#/
|
337
|
+
to.split("#")
|
338
|
+
else
|
339
|
+
[]
|
340
|
+
end
|
199
341
|
end
|
200
342
|
|
201
|
-
def
|
202
|
-
if
|
203
|
-
|
204
|
-
|
343
|
+
def add_controller_module(controller, modyoule)
|
344
|
+
if modyoule && !controller.is_a?(Regexp)
|
345
|
+
if controller =~ %r{\A/}
|
346
|
+
controller[1..-1]
|
347
|
+
else
|
348
|
+
[modyoule, controller].compact.join("/")
|
349
|
+
end
|
205
350
|
else
|
206
|
-
|
351
|
+
controller
|
207
352
|
end
|
208
353
|
end
|
209
354
|
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
355
|
+
def translate_controller(controller)
|
356
|
+
return controller if Regexp === controller
|
357
|
+
return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
|
358
|
+
|
359
|
+
yield
|
214
360
|
end
|
215
361
|
|
216
|
-
def
|
217
|
-
|
362
|
+
def blocks(callable_constraint)
|
363
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
364
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
365
|
+
end
|
366
|
+
[callable_constraint]
|
218
367
|
end
|
219
368
|
|
220
|
-
def
|
221
|
-
|
369
|
+
def constraints(options, path_params)
|
370
|
+
options.group_by do |key, option|
|
371
|
+
if Regexp === option
|
372
|
+
:constraints
|
373
|
+
else
|
374
|
+
if path_params.include?(key)
|
375
|
+
:path_params
|
376
|
+
else
|
377
|
+
:required_defaults
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
222
381
|
end
|
223
382
|
|
224
|
-
def
|
225
|
-
|
383
|
+
def dispatcher(raise_on_name_error)
|
384
|
+
Routing::RouteSet::Dispatcher.new raise_on_name_error
|
226
385
|
end
|
227
386
|
end
|
228
387
|
|
229
|
-
# Invokes
|
388
|
+
# Invokes Journey::Router::Utils.normalize_path and ensure that
|
230
389
|
# (:locale) becomes (/:locale) instead of /(:locale). Except
|
231
390
|
# for root cases, where the latter is the correct one.
|
232
391
|
def self.normalize_path(path)
|
233
392
|
path = Journey::Router::Utils.normalize_path(path)
|
234
|
-
path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{
|
393
|
+
path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/(\(+[^)]+\)){1,}$}
|
235
394
|
path
|
236
395
|
end
|
237
396
|
|
238
397
|
def self.normalize_name(name)
|
239
|
-
normalize_path(name)[1..-1].
|
398
|
+
normalize_path(name)[1..-1].tr("/", "_")
|
240
399
|
end
|
241
400
|
|
242
401
|
module Base
|
243
|
-
#
|
402
|
+
# Matches a URL pattern to one or more routes.
|
244
403
|
#
|
245
|
-
#
|
404
|
+
# You should not use the +match+ method in your router
|
405
|
+
# without specifying an HTTP method.
|
246
406
|
#
|
247
|
-
#
|
248
|
-
#
|
249
|
-
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
250
|
-
# because this means it will be matched first. As this is the most popular route
|
251
|
-
# of most Rails applications, this is beneficial.
|
252
|
-
def root(options = {})
|
253
|
-
match '/', { :as => :root }.merge(options)
|
254
|
-
end
|
255
|
-
|
256
|
-
# Matches a url pattern to one or more routes. Any symbols in a pattern
|
257
|
-
# are interpreted as url query parameters and thus available as +params+
|
258
|
-
# in an action:
|
407
|
+
# If you want to expose your action to both GET and POST, use:
|
259
408
|
#
|
260
409
|
# # sets :controller, :action and :id in params
|
261
|
-
# match ':controller/:action/:id'
|
410
|
+
# match ':controller/:action/:id', via: [:get, :post]
|
411
|
+
#
|
412
|
+
# Note that +:controller+, +:action+ and +:id+ are interpreted as URL
|
413
|
+
# query parameters and thus available through +params+ in an action.
|
414
|
+
#
|
415
|
+
# If you want to expose your action to GET, use +get+ in the router:
|
416
|
+
#
|
417
|
+
# Instead of:
|
418
|
+
#
|
419
|
+
# match ":controller/:action/:id"
|
420
|
+
#
|
421
|
+
# Do:
|
422
|
+
#
|
423
|
+
# get ":controller/:action/:id"
|
262
424
|
#
|
263
425
|
# Two of these symbols are special, +:controller+ maps to the controller
|
264
426
|
# and +:action+ to the controller's action. A pattern can also map
|
265
427
|
# wildcard segments (globs) to params:
|
266
428
|
#
|
267
|
-
#
|
429
|
+
# get 'songs/*category/:title', to: 'songs#show'
|
268
430
|
#
|
269
431
|
# # 'songs/rock/classic/stairway-to-heaven' sets
|
270
432
|
# # params[:category] = 'rock/classic'
|
271
433
|
# # params[:title] = 'stairway-to-heaven'
|
272
434
|
#
|
435
|
+
# To match a wildcard parameter, it must have a name assigned to it.
|
436
|
+
# Without a variable name to attach the glob parameter to, the route
|
437
|
+
# can't be parsed.
|
438
|
+
#
|
273
439
|
# When a pattern points to an internal route, the route's +:action+ and
|
274
440
|
# +:controller+ should be set in options or hash shorthand. Examples:
|
275
441
|
#
|
276
|
-
# match 'photos/:id' => 'photos#show'
|
277
|
-
# match 'photos/:id', :
|
278
|
-
# match 'photos/:id', :
|
442
|
+
# match 'photos/:id' => 'photos#show', via: :get
|
443
|
+
# match 'photos/:id', to: 'photos#show', via: :get
|
444
|
+
# match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
279
445
|
#
|
280
446
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
281
447
|
# responds to +call+:
|
282
448
|
#
|
283
|
-
# match 'photos/:id'
|
284
|
-
# match 'photos/:id'
|
449
|
+
# match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
|
450
|
+
# match 'photos/:id', to: PhotoRackApp, via: :get
|
285
451
|
# # Yes, controller actions are just rack endpoints
|
286
|
-
# match 'photos/:id'
|
452
|
+
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
453
|
+
#
|
454
|
+
# Because requesting various HTTP verbs with a single action has security
|
455
|
+
# implications, you must either specify the actions in
|
456
|
+
# the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
|
457
|
+
# instead +match+
|
287
458
|
#
|
288
459
|
# === Options
|
289
460
|
#
|
290
|
-
# Any options not seen here are passed on as params with the
|
461
|
+
# Any options not seen here are passed on as params with the URL.
|
291
462
|
#
|
292
463
|
# [:controller]
|
293
464
|
# The route's controller.
|
@@ -295,14 +466,45 @@ module ActionDispatch
|
|
295
466
|
# [:action]
|
296
467
|
# The route's action.
|
297
468
|
#
|
469
|
+
# [:param]
|
470
|
+
# Overrides the default resource identifier +:id+ (name of the
|
471
|
+
# dynamic segment used to generate the routes).
|
472
|
+
# You can access that segment from your controller using
|
473
|
+
# <tt>params[<:param>]</tt>.
|
474
|
+
# In your router:
|
475
|
+
#
|
476
|
+
# resources :users, param: :name
|
477
|
+
#
|
478
|
+
# The +users+ resource here will have the following routes generated for it:
|
479
|
+
#
|
480
|
+
# GET /users(.:format)
|
481
|
+
# POST /users(.:format)
|
482
|
+
# GET /users/new(.:format)
|
483
|
+
# GET /users/:name/edit(.:format)
|
484
|
+
# GET /users/:name(.:format)
|
485
|
+
# PATCH/PUT /users/:name(.:format)
|
486
|
+
# DELETE /users/:name(.:format)
|
487
|
+
#
|
488
|
+
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
|
489
|
+
# model to construct a URL:
|
490
|
+
#
|
491
|
+
# class User < ActiveRecord::Base
|
492
|
+
# def to_param
|
493
|
+
# name
|
494
|
+
# end
|
495
|
+
# end
|
496
|
+
#
|
497
|
+
# user = User.find_by(name: 'Phusion')
|
498
|
+
# user_path(user) # => "/users/Phusion"
|
499
|
+
#
|
298
500
|
# [:path]
|
299
501
|
# The path prefix for the routes.
|
300
502
|
#
|
301
503
|
# [:module]
|
302
504
|
# The namespace for :controller.
|
303
505
|
#
|
304
|
-
# match 'path'
|
305
|
-
#
|
506
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
|
507
|
+
# # => Sekret::PostsController
|
306
508
|
#
|
307
509
|
# See <tt>Scoping#namespace</tt> for its scope equivalent.
|
308
510
|
#
|
@@ -312,16 +514,17 @@ module ActionDispatch
|
|
312
514
|
# [:via]
|
313
515
|
# Allowed HTTP verb(s) for route.
|
314
516
|
#
|
315
|
-
# match 'path'
|
316
|
-
# match 'path'
|
517
|
+
# match 'path', to: 'c#a', via: :get
|
518
|
+
# match 'path', to: 'c#a', via: [:get, :post]
|
519
|
+
# match 'path', to: 'c#a', via: :all
|
317
520
|
#
|
318
521
|
# [:to]
|
319
522
|
# Points to a +Rack+ endpoint. Can be an object that responds to
|
320
523
|
# +call+ or a string representing a controller's action.
|
321
524
|
#
|
322
|
-
# match 'path', :
|
323
|
-
# match 'path', :
|
324
|
-
# match 'path', :
|
525
|
+
# match 'path', to: 'controller#action', via: :get
|
526
|
+
# match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
|
527
|
+
# match 'path', to: RackApp, via: :get
|
325
528
|
#
|
326
529
|
# [:on]
|
327
530
|
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
@@ -329,27 +532,31 @@ module ActionDispatch
|
|
329
532
|
# <tt>resource(s)</tt> block. For example:
|
330
533
|
#
|
331
534
|
# resource :bar do
|
332
|
-
# match 'foo'
|
535
|
+
# match 'foo', to: 'c#a', on: :member, via: [:get, :post]
|
333
536
|
# end
|
334
537
|
#
|
335
538
|
# Is equivalent to:
|
336
539
|
#
|
337
540
|
# resource :bar do
|
338
541
|
# member do
|
339
|
-
# match 'foo'
|
542
|
+
# match 'foo', to: 'c#a', via: [:get, :post]
|
340
543
|
# end
|
341
544
|
# end
|
342
545
|
#
|
343
546
|
# [:constraints]
|
344
|
-
# Constrains parameters with a hash of regular expressions
|
345
|
-
# object that responds to <tt>matches?</tt
|
547
|
+
# Constrains parameters with a hash of regular expressions
|
548
|
+
# or an object that responds to <tt>matches?</tt>. In addition, constraints
|
549
|
+
# other than path can also be specified with any object
|
550
|
+
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
|
551
|
+
#
|
552
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
346
553
|
#
|
347
|
-
# match '
|
554
|
+
# match 'json_only', constraints: { format: 'json' }, via: :get
|
348
555
|
#
|
349
|
-
# class
|
556
|
+
# class Whitelist
|
350
557
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
351
558
|
# end
|
352
|
-
# match 'path'
|
559
|
+
# match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
|
353
560
|
#
|
354
561
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
355
562
|
# equivalent.
|
@@ -358,7 +565,7 @@ module ActionDispatch
|
|
358
565
|
# Sets defaults for parameters
|
359
566
|
#
|
360
567
|
# # Sets params[:format] to 'jpg' by default
|
361
|
-
# match 'path'
|
568
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
|
362
569
|
#
|
363
570
|
# See <tt>Scoping#defaults</tt> for its scope equivalent.
|
364
571
|
#
|
@@ -367,13 +574,17 @@ module ActionDispatch
|
|
367
574
|
# false, the pattern matches any request prefixed with the given path.
|
368
575
|
#
|
369
576
|
# # Matches any request starting with 'path'
|
370
|
-
# match 'path'
|
371
|
-
|
577
|
+
# match 'path', to: 'c#a', anchor: false, via: :get
|
578
|
+
#
|
579
|
+
# [:format]
|
580
|
+
# Allows you to specify the default value for optional +format+
|
581
|
+
# segment or disable it by supplying +false+.
|
582
|
+
def match(path, options = nil)
|
372
583
|
end
|
373
584
|
|
374
585
|
# Mount a Rack-based application to be used within the application.
|
375
586
|
#
|
376
|
-
# mount SomeRackApp, :
|
587
|
+
# mount SomeRackApp, at: "some_route"
|
377
588
|
#
|
378
589
|
# Alternatively:
|
379
590
|
#
|
@@ -386,26 +597,37 @@ module ActionDispatch
|
|
386
597
|
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
|
387
598
|
# To customize this helper's name, use the +:as+ option:
|
388
599
|
#
|
389
|
-
# mount(SomeRackApp => "some_route", :
|
600
|
+
# mount(SomeRackApp => "some_route", as: "exciting")
|
390
601
|
#
|
391
602
|
# This will generate the +exciting_path+ and +exciting_url+ helpers
|
392
603
|
# which can be used to navigate to this mounted app.
|
393
604
|
def mount(app, options = nil)
|
394
605
|
if options
|
395
606
|
path = options.delete(:at)
|
396
|
-
|
607
|
+
elsif Hash === app
|
397
608
|
options = app
|
398
|
-
app, path = options.find { |k,
|
609
|
+
app, path = options.find { |k, _| k.respond_to?(:call) }
|
399
610
|
options.delete(app) if app
|
400
611
|
end
|
401
612
|
|
402
|
-
raise "A rack application must be specified" unless
|
613
|
+
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
614
|
+
raise ArgumentError, <<-MSG.strip_heredoc unless path
|
615
|
+
Must be called with mount point
|
403
616
|
|
404
|
-
|
617
|
+
mount SomeRackApp, at: "some_route"
|
618
|
+
or
|
619
|
+
mount(SomeRackApp => "some_route")
|
620
|
+
MSG
|
405
621
|
|
406
|
-
|
622
|
+
rails_app = rails_app? app
|
623
|
+
options[:as] ||= app_name(app, rails_app)
|
407
624
|
|
408
|
-
|
625
|
+
target_as = name_for_action(options[:as], path)
|
626
|
+
options[:via] ||= :all
|
627
|
+
|
628
|
+
match(path, options.merge(to: app, anchor: false, format: false))
|
629
|
+
|
630
|
+
define_generate_prefix(app, target_as) if rails_app
|
409
631
|
self
|
410
632
|
end
|
411
633
|
|
@@ -420,84 +642,105 @@ module ActionDispatch
|
|
420
642
|
end
|
421
643
|
end
|
422
644
|
|
645
|
+
# Query if the following named route was already defined.
|
646
|
+
def has_named_route?(name)
|
647
|
+
@set.named_routes.key? name
|
648
|
+
end
|
649
|
+
|
423
650
|
private
|
424
|
-
def
|
425
|
-
|
651
|
+
def rails_app?(app)
|
652
|
+
app.is_a?(Class) && app < Rails::Railtie
|
653
|
+
end
|
426
654
|
|
427
|
-
|
655
|
+
def app_name(app, rails_app)
|
656
|
+
if rails_app
|
428
657
|
app.railtie_name
|
429
|
-
|
430
|
-
class_name = app.
|
431
|
-
ActiveSupport::Inflector.underscore(class_name).
|
658
|
+
elsif app.is_a?(Class)
|
659
|
+
class_name = app.name
|
660
|
+
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
|
432
661
|
end
|
433
662
|
end
|
434
663
|
|
435
664
|
def define_generate_prefix(app, name)
|
436
|
-
|
437
|
-
|
438
|
-
_route = @set.named_routes.routes[name.to_sym]
|
665
|
+
_route = @set.named_routes.get name
|
439
666
|
_routes = @set
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
prefix
|
667
|
+
_url_helpers = @set.url_helpers
|
668
|
+
|
669
|
+
script_namer = ->(options) do
|
670
|
+
prefix_options = options.slice(*_route.segment_keys)
|
671
|
+
prefix_options[:relative_url_root] = "".freeze
|
672
|
+
|
673
|
+
if options[:_recall]
|
674
|
+
prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
|
449
675
|
end
|
676
|
+
|
677
|
+
# We must actually delete prefix segment keys to avoid passing them to next url_for.
|
678
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
679
|
+
_url_helpers.send("#{name}_path", prefix_options)
|
450
680
|
end
|
681
|
+
|
682
|
+
app.routes.define_mounted_helper(name, script_namer)
|
683
|
+
|
684
|
+
app.routes.extend Module.new {
|
685
|
+
def optimize_routes_generation?; false; end
|
686
|
+
|
687
|
+
define_method :find_script_name do |options|
|
688
|
+
if options.key? :script_name
|
689
|
+
super(options)
|
690
|
+
else
|
691
|
+
script_namer.call(options)
|
692
|
+
end
|
693
|
+
end
|
694
|
+
}
|
451
695
|
end
|
452
696
|
end
|
453
697
|
|
454
698
|
module HttpHelpers
|
455
699
|
# Define a route that only recognizes HTTP GET.
|
456
|
-
# For supported arguments, see
|
457
|
-
#
|
458
|
-
# Example:
|
700
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
459
701
|
#
|
460
|
-
#
|
702
|
+
# get 'bacon', to: 'food#bacon'
|
461
703
|
def get(*args, &block)
|
462
|
-
map_method(:get,
|
704
|
+
map_method(:get, args, &block)
|
463
705
|
end
|
464
706
|
|
465
707
|
# Define a route that only recognizes HTTP POST.
|
466
|
-
# For supported arguments, see
|
467
|
-
#
|
468
|
-
# Example:
|
708
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
469
709
|
#
|
470
|
-
#
|
710
|
+
# post 'bacon', to: 'food#bacon'
|
471
711
|
def post(*args, &block)
|
472
|
-
map_method(:post,
|
712
|
+
map_method(:post, args, &block)
|
473
713
|
end
|
474
714
|
|
475
|
-
# Define a route that only recognizes HTTP
|
476
|
-
# For supported arguments, see
|
477
|
-
#
|
478
|
-
# Example:
|
715
|
+
# Define a route that only recognizes HTTP PATCH.
|
716
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
479
717
|
#
|
480
|
-
#
|
481
|
-
def
|
482
|
-
map_method(:
|
718
|
+
# patch 'bacon', to: 'food#bacon'
|
719
|
+
def patch(*args, &block)
|
720
|
+
map_method(:patch, args, &block)
|
483
721
|
end
|
484
722
|
|
485
723
|
# Define a route that only recognizes HTTP PUT.
|
486
|
-
# For supported arguments, see
|
724
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
487
725
|
#
|
488
|
-
#
|
726
|
+
# put 'bacon', to: 'food#bacon'
|
727
|
+
def put(*args, &block)
|
728
|
+
map_method(:put, args, &block)
|
729
|
+
end
|
730
|
+
|
731
|
+
# Define a route that only recognizes HTTP DELETE.
|
732
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
489
733
|
#
|
490
|
-
#
|
734
|
+
# delete 'broccoli', to: 'food#broccoli'
|
491
735
|
def delete(*args, &block)
|
492
|
-
map_method(:delete,
|
736
|
+
map_method(:delete, args, &block)
|
493
737
|
end
|
494
738
|
|
495
739
|
private
|
496
|
-
def map_method(method,
|
740
|
+
def map_method(method, args, &block)
|
497
741
|
options = args.extract_options!
|
498
742
|
options[:via] = method
|
499
|
-
args
|
500
|
-
match(*args, &block)
|
743
|
+
match(*args, options, &block)
|
501
744
|
self
|
502
745
|
end
|
503
746
|
end
|
@@ -515,27 +758,27 @@ module ActionDispatch
|
|
515
758
|
# This will create a number of routes for each of the posts and comments
|
516
759
|
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
|
517
760
|
#
|
518
|
-
# GET
|
519
|
-
# GET
|
520
|
-
# POST
|
521
|
-
# GET
|
522
|
-
# GET
|
523
|
-
# PUT
|
524
|
-
# DELETE
|
761
|
+
# GET /admin/posts
|
762
|
+
# GET /admin/posts/new
|
763
|
+
# POST /admin/posts
|
764
|
+
# GET /admin/posts/1
|
765
|
+
# GET /admin/posts/1/edit
|
766
|
+
# PATCH/PUT /admin/posts/1
|
767
|
+
# DELETE /admin/posts/1
|
525
768
|
#
|
526
769
|
# If you want to route /posts (without the prefix /admin) to
|
527
770
|
# <tt>Admin::PostsController</tt>, you could use
|
528
771
|
#
|
529
|
-
# scope :
|
772
|
+
# scope module: "admin" do
|
530
773
|
# resources :posts
|
531
774
|
# end
|
532
775
|
#
|
533
776
|
# or, for a single case
|
534
777
|
#
|
535
|
-
# resources :posts, :
|
778
|
+
# resources :posts, module: "admin"
|
536
779
|
#
|
537
780
|
# If you want to route /admin/posts to +PostsController+
|
538
|
-
# (without the Admin
|
781
|
+
# (without the <tt>Admin::</tt> module prefix), you could use
|
539
782
|
#
|
540
783
|
# scope "/admin" do
|
541
784
|
# resources :posts
|
@@ -543,25 +786,25 @@ module ActionDispatch
|
|
543
786
|
#
|
544
787
|
# or, for a single case
|
545
788
|
#
|
546
|
-
# resources :posts, :
|
789
|
+
# resources :posts, path: "/admin/posts"
|
547
790
|
#
|
548
791
|
# In each of these cases, the named routes remain the same as if you did
|
549
792
|
# not use scope. In the last case, the following paths map to
|
550
793
|
# +PostsController+:
|
551
794
|
#
|
552
|
-
# GET
|
553
|
-
# GET
|
554
|
-
# POST
|
555
|
-
# GET
|
556
|
-
# GET
|
557
|
-
# PUT
|
558
|
-
# DELETE
|
795
|
+
# GET /admin/posts
|
796
|
+
# GET /admin/posts/new
|
797
|
+
# POST /admin/posts
|
798
|
+
# GET /admin/posts/1
|
799
|
+
# GET /admin/posts/1/edit
|
800
|
+
# PATCH/PUT /admin/posts/1
|
801
|
+
# DELETE /admin/posts/1
|
559
802
|
module Scoping
|
560
803
|
# Scopes a set of routes to the given default options.
|
561
804
|
#
|
562
805
|
# Take the following route definition as an example:
|
563
806
|
#
|
564
|
-
# scope :
|
807
|
+
# scope path: ":account_id", as: "account" do
|
565
808
|
# resources :projects
|
566
809
|
# end
|
567
810
|
#
|
@@ -573,67 +816,84 @@ module ActionDispatch
|
|
573
816
|
#
|
574
817
|
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
|
575
818
|
#
|
576
|
-
# === Examples
|
577
|
-
#
|
578
819
|
# # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
|
579
|
-
# scope :
|
820
|
+
# scope module: "admin" do
|
580
821
|
# resources :posts
|
581
822
|
# end
|
582
823
|
#
|
583
824
|
# # prefix the posts resource's requests with '/admin'
|
584
|
-
# scope :
|
825
|
+
# scope path: "/admin" do
|
585
826
|
# resources :posts
|
586
827
|
# end
|
587
828
|
#
|
588
829
|
# # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
|
589
|
-
# scope :
|
830
|
+
# scope as: "sekret" do
|
590
831
|
# resources :posts
|
591
832
|
# end
|
592
833
|
def scope(*args)
|
593
|
-
options = args.extract_options
|
594
|
-
|
595
|
-
|
596
|
-
options[:path] = args.first if args.first.is_a?(String)
|
597
|
-
recover = {}
|
834
|
+
options = args.extract_options!.dup
|
835
|
+
scope = {}
|
598
836
|
|
837
|
+
options[:path] = args.flatten.join("/") if args.any?
|
599
838
|
options[:constraints] ||= {}
|
600
|
-
|
601
|
-
|
839
|
+
|
840
|
+
unless nested_scope?
|
841
|
+
options[:shallow_path] ||= options[:path] if options.key?(:path)
|
842
|
+
options[:shallow_prefix] ||= options[:as] if options.key?(:as)
|
602
843
|
end
|
603
844
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
845
|
+
if options[:constraints].is_a?(Hash)
|
846
|
+
defaults = options[:constraints].select do |k, v|
|
847
|
+
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
608
848
|
end
|
849
|
+
|
850
|
+
options[:defaults] = defaults.merge(options[:defaults] || {})
|
851
|
+
else
|
852
|
+
block, options[:constraints] = options[:constraints], {}
|
609
853
|
end
|
610
854
|
|
611
|
-
|
612
|
-
|
855
|
+
if options.key?(:only) || options.key?(:except)
|
856
|
+
scope[:action_options] = { only: options.delete(:only),
|
857
|
+
except: options.delete(:except) }
|
858
|
+
end
|
859
|
+
|
860
|
+
if options.key? :anchor
|
861
|
+
raise ArgumentError, "anchor is ignored unless passed to `match`"
|
862
|
+
end
|
613
863
|
|
614
|
-
|
615
|
-
|
864
|
+
@scope.options.each do |option|
|
865
|
+
if option == :blocks
|
866
|
+
value = block
|
867
|
+
elsif option == :options
|
868
|
+
value = options
|
869
|
+
else
|
870
|
+
value = options.delete(option) { POISON }
|
871
|
+
end
|
872
|
+
|
873
|
+
unless POISON == value
|
874
|
+
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
875
|
+
end
|
876
|
+
end
|
616
877
|
|
878
|
+
@scope = @scope.new scope
|
617
879
|
yield
|
618
880
|
self
|
619
881
|
ensure
|
620
|
-
|
621
|
-
@scope[option] = recover[option] if recover.has_key?(option)
|
622
|
-
end
|
623
|
-
|
624
|
-
@scope[:options] = recover[:options]
|
625
|
-
@scope[:blocks] = recover[:block]
|
882
|
+
@scope = @scope.parent
|
626
883
|
end
|
627
884
|
|
885
|
+
POISON = Object.new # :nodoc:
|
886
|
+
|
628
887
|
# Scopes routes to a specific controller
|
629
888
|
#
|
630
|
-
# Example:
|
631
889
|
# controller "food" do
|
632
|
-
# match "bacon", :
|
890
|
+
# match "bacon", action: :bacon, via: :get
|
633
891
|
# end
|
634
|
-
def controller(controller
|
635
|
-
|
636
|
-
|
892
|
+
def controller(controller)
|
893
|
+
@scope = @scope.new(controller: controller)
|
894
|
+
yield
|
895
|
+
ensure
|
896
|
+
@scope = @scope.parent
|
637
897
|
end
|
638
898
|
|
639
899
|
# Scopes routes to a specific namespace. For example:
|
@@ -644,13 +904,13 @@ module ActionDispatch
|
|
644
904
|
#
|
645
905
|
# This generates the following routes:
|
646
906
|
#
|
647
|
-
# admin_posts GET
|
648
|
-
# admin_posts POST
|
649
|
-
# new_admin_post GET
|
650
|
-
# edit_admin_post GET
|
651
|
-
# admin_post GET
|
652
|
-
# admin_post PUT
|
653
|
-
# admin_post DELETE
|
907
|
+
# admin_posts GET /admin/posts(.:format) admin/posts#index
|
908
|
+
# admin_posts POST /admin/posts(.:format) admin/posts#create
|
909
|
+
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
|
910
|
+
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
|
911
|
+
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
|
912
|
+
# admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
|
913
|
+
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
|
654
914
|
#
|
655
915
|
# === Options
|
656
916
|
#
|
@@ -660,34 +920,40 @@ module ActionDispatch
|
|
660
920
|
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
|
661
921
|
# <tt>Resources#resources</tt>.
|
662
922
|
#
|
663
|
-
# === Examples
|
664
|
-
#
|
665
923
|
# # accessible through /sekret/posts rather than /admin/posts
|
666
|
-
# namespace :admin, :
|
924
|
+
# namespace :admin, path: "sekret" do
|
667
925
|
# resources :posts
|
668
926
|
# end
|
669
927
|
#
|
670
928
|
# # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
|
671
|
-
# namespace :admin, :
|
929
|
+
# namespace :admin, module: "sekret" do
|
672
930
|
# resources :posts
|
673
931
|
# end
|
674
932
|
#
|
675
933
|
# # generates +sekret_posts_path+ rather than +admin_posts_path+
|
676
|
-
# namespace :admin, :
|
934
|
+
# namespace :admin, as: "sekret" do
|
677
935
|
# resources :posts
|
678
936
|
# end
|
679
937
|
def namespace(path, options = {})
|
680
938
|
path = path.to_s
|
681
|
-
|
682
|
-
|
683
|
-
|
939
|
+
|
940
|
+
defaults = {
|
941
|
+
module: path,
|
942
|
+
as: options.fetch(:as, path),
|
943
|
+
shallow_path: options.fetch(:path, path),
|
944
|
+
shallow_prefix: options.fetch(:as, path)
|
945
|
+
}
|
946
|
+
|
947
|
+
path_scope(options.delete(:path) { path }) do
|
948
|
+
scope(defaults.merge!(options)) { yield }
|
949
|
+
end
|
684
950
|
end
|
685
951
|
|
686
952
|
# === Parameter Restriction
|
687
953
|
# Allows you to constrain the nested routes based on a set of rules.
|
688
954
|
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
|
689
955
|
#
|
690
|
-
# constraints(:
|
956
|
+
# constraints(id: /\d+\.\d+/) do
|
691
957
|
# resources :posts
|
692
958
|
# end
|
693
959
|
#
|
@@ -697,7 +963,7 @@ module ActionDispatch
|
|
697
963
|
# You may use this to also restrict other parameters:
|
698
964
|
#
|
699
965
|
# resources :posts do
|
700
|
-
# constraints(:
|
966
|
+
# constraints(post_id: /\d+\.\d+/) do
|
701
967
|
# resources :comments
|
702
968
|
# end
|
703
969
|
# end
|
@@ -706,7 +972,7 @@ module ActionDispatch
|
|
706
972
|
#
|
707
973
|
# Routes can also be constrained to an IP or a certain range of IP addresses:
|
708
974
|
#
|
709
|
-
# constraints(:
|
975
|
+
# constraints(ip: /192\.168\.\d+\.\d+/) do
|
710
976
|
# resources :posts
|
711
977
|
# end
|
712
978
|
#
|
@@ -717,7 +983,7 @@ module ActionDispatch
|
|
717
983
|
#
|
718
984
|
# Requests to routes can be constrained based on specific criteria:
|
719
985
|
#
|
720
|
-
# constraints(
|
986
|
+
# constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
|
721
987
|
# resources :iphones
|
722
988
|
# end
|
723
989
|
#
|
@@ -739,79 +1005,86 @@ module ActionDispatch
|
|
739
1005
|
# resources :iphones
|
740
1006
|
# end
|
741
1007
|
def constraints(constraints = {})
|
742
|
-
scope(:
|
1008
|
+
scope(constraints: constraints) { yield }
|
743
1009
|
end
|
744
1010
|
|
745
1011
|
# Allows you to set default parameters for a route, such as this:
|
746
|
-
# defaults :
|
747
|
-
# match 'scoped_pages/(:id)', :
|
1012
|
+
# defaults id: 'home' do
|
1013
|
+
# match 'scoped_pages/(:id)', to: 'pages#show'
|
748
1014
|
# end
|
749
1015
|
# Using this, the +:id+ parameter here will default to 'home'.
|
750
1016
|
def defaults(defaults = {})
|
751
|
-
scope(:defaults
|
1017
|
+
@scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
|
1018
|
+
yield
|
1019
|
+
ensure
|
1020
|
+
@scope = @scope.parent
|
752
1021
|
end
|
753
1022
|
|
754
1023
|
private
|
755
|
-
def
|
756
|
-
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
|
757
|
-
end
|
758
|
-
|
759
|
-
def merge_path_scope(parent, child) #:nodoc:
|
1024
|
+
def merge_path_scope(parent, child)
|
760
1025
|
Mapper.normalize_path("#{parent}/#{child}")
|
761
1026
|
end
|
762
1027
|
|
763
|
-
def merge_shallow_path_scope(parent, child)
|
1028
|
+
def merge_shallow_path_scope(parent, child)
|
764
1029
|
Mapper.normalize_path("#{parent}/#{child}")
|
765
1030
|
end
|
766
1031
|
|
767
|
-
def merge_as_scope(parent, child)
|
1032
|
+
def merge_as_scope(parent, child)
|
768
1033
|
parent ? "#{parent}_#{child}" : child
|
769
1034
|
end
|
770
1035
|
|
771
|
-
def merge_shallow_prefix_scope(parent, child)
|
1036
|
+
def merge_shallow_prefix_scope(parent, child)
|
772
1037
|
parent ? "#{parent}_#{child}" : child
|
773
1038
|
end
|
774
1039
|
|
775
|
-
def merge_module_scope(parent, child)
|
1040
|
+
def merge_module_scope(parent, child)
|
776
1041
|
parent ? "#{parent}/#{child}" : child
|
777
1042
|
end
|
778
1043
|
|
779
|
-
def merge_controller_scope(parent, child)
|
1044
|
+
def merge_controller_scope(parent, child)
|
1045
|
+
child
|
1046
|
+
end
|
1047
|
+
|
1048
|
+
def merge_action_scope(parent, child)
|
1049
|
+
child
|
1050
|
+
end
|
1051
|
+
|
1052
|
+
def merge_via_scope(parent, child)
|
780
1053
|
child
|
781
1054
|
end
|
782
1055
|
|
783
|
-
def
|
1056
|
+
def merge_format_scope(parent, child)
|
784
1057
|
child
|
785
1058
|
end
|
786
1059
|
|
787
|
-
def merge_path_names_scope(parent, child)
|
1060
|
+
def merge_path_names_scope(parent, child)
|
788
1061
|
merge_options_scope(parent, child)
|
789
1062
|
end
|
790
1063
|
|
791
|
-
def merge_constraints_scope(parent, child)
|
1064
|
+
def merge_constraints_scope(parent, child)
|
792
1065
|
merge_options_scope(parent, child)
|
793
1066
|
end
|
794
1067
|
|
795
|
-
def merge_defaults_scope(parent, child)
|
1068
|
+
def merge_defaults_scope(parent, child)
|
796
1069
|
merge_options_scope(parent, child)
|
797
1070
|
end
|
798
1071
|
|
799
|
-
def merge_blocks_scope(parent, child)
|
1072
|
+
def merge_blocks_scope(parent, child)
|
800
1073
|
merged = parent ? parent.dup : []
|
801
1074
|
merged << child if child
|
802
1075
|
merged
|
803
1076
|
end
|
804
1077
|
|
805
|
-
def merge_options_scope(parent, child)
|
806
|
-
(parent || {}).
|
1078
|
+
def merge_options_scope(parent, child)
|
1079
|
+
(parent || {}).merge(child)
|
807
1080
|
end
|
808
1081
|
|
809
|
-
def merge_shallow_scope(parent, child)
|
1082
|
+
def merge_shallow_scope(parent, child)
|
810
1083
|
child ? true : false
|
811
1084
|
end
|
812
1085
|
|
813
|
-
def
|
814
|
-
child
|
1086
|
+
def merge_to_scope(parent, child)
|
1087
|
+
child
|
815
1088
|
end
|
816
1089
|
end
|
817
1090
|
|
@@ -850,7 +1123,7 @@ module ActionDispatch
|
|
850
1123
|
# use dots as part of the +:id+ parameter add a constraint which
|
851
1124
|
# overrides this restriction, e.g:
|
852
1125
|
#
|
853
|
-
# resources :articles, :
|
1126
|
+
# resources :articles, id: /[^\/]+/
|
854
1127
|
#
|
855
1128
|
# This allows any character other than a slash as part of your +:id+.
|
856
1129
|
#
|
@@ -858,29 +1131,38 @@ module ActionDispatch
|
|
858
1131
|
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
|
859
1132
|
# a path appended since they fit properly in their scope level.
|
860
1133
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
861
|
-
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
|
1134
|
+
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
862
1135
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
863
1136
|
|
864
1137
|
class Resource #:nodoc:
|
865
|
-
attr_reader :controller, :path, :
|
1138
|
+
attr_reader :controller, :path, :param
|
866
1139
|
|
867
|
-
def initialize(entities, options = {})
|
1140
|
+
def initialize(entities, api_only, shallow, options = {})
|
868
1141
|
@name = entities.to_s
|
869
1142
|
@path = (options[:path] || @name).to_s
|
870
1143
|
@controller = (options[:controller] || @name).to_s
|
871
1144
|
@as = options[:as]
|
1145
|
+
@param = (options[:param] || :id).to_sym
|
872
1146
|
@options = options
|
1147
|
+
@shallow = shallow
|
1148
|
+
@api_only = api_only
|
1149
|
+
@only = options.delete :only
|
1150
|
+
@except = options.delete :except
|
873
1151
|
end
|
874
1152
|
|
875
1153
|
def default_actions
|
876
|
-
|
1154
|
+
if @api_only
|
1155
|
+
[:index, :create, :show, :update, :destroy]
|
1156
|
+
else
|
1157
|
+
[:index, :create, :new, :show, :update, :destroy, :edit]
|
1158
|
+
end
|
877
1159
|
end
|
878
1160
|
|
879
1161
|
def actions
|
880
|
-
if
|
881
|
-
Array(only).map(&:to_sym)
|
882
|
-
elsif
|
883
|
-
default_actions - Array(except).map(&:to_sym)
|
1162
|
+
if @only
|
1163
|
+
Array(@only).map(&:to_sym)
|
1164
|
+
elsif @except
|
1165
|
+
default_actions - Array(@except).map(&:to_sym)
|
884
1166
|
else
|
885
1167
|
default_actions
|
886
1168
|
end
|
@@ -907,27 +1189,38 @@ module ActionDispatch
|
|
907
1189
|
end
|
908
1190
|
|
909
1191
|
def resource_scope
|
910
|
-
|
1192
|
+
controller
|
911
1193
|
end
|
912
1194
|
|
913
1195
|
alias :collection_scope :path
|
914
1196
|
|
915
1197
|
def member_scope
|
916
|
-
"#{path}
|
1198
|
+
"#{path}/:#{param}"
|
917
1199
|
end
|
918
1200
|
|
1201
|
+
alias :shallow_scope :member_scope
|
1202
|
+
|
919
1203
|
def new_scope(new_path)
|
920
1204
|
"#{path}/#{new_path}"
|
921
1205
|
end
|
922
1206
|
|
1207
|
+
def nested_param
|
1208
|
+
:"#{singular}_#{param}"
|
1209
|
+
end
|
1210
|
+
|
923
1211
|
def nested_scope
|
924
|
-
"#{path}/:#{
|
1212
|
+
"#{path}/:#{nested_param}"
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
def shallow?
|
1216
|
+
@shallow
|
925
1217
|
end
|
926
1218
|
|
1219
|
+
def singleton?; false; end
|
927
1220
|
end
|
928
1221
|
|
929
1222
|
class SingletonResource < Resource #:nodoc:
|
930
|
-
def initialize(entities, options)
|
1223
|
+
def initialize(entities, api_only, shallow, options)
|
931
1224
|
super
|
932
1225
|
@as = nil
|
933
1226
|
@controller = (options[:controller] || plural).to_s
|
@@ -935,7 +1228,11 @@ module ActionDispatch
|
|
935
1228
|
end
|
936
1229
|
|
937
1230
|
def default_actions
|
938
|
-
|
1231
|
+
if @api_only
|
1232
|
+
[:show, :create, :update, :destroy]
|
1233
|
+
else
|
1234
|
+
[:show, :create, :update, :destroy, :new, :edit]
|
1235
|
+
end
|
939
1236
|
end
|
940
1237
|
|
941
1238
|
def plural
|
@@ -951,6 +1248,8 @@ module ActionDispatch
|
|
951
1248
|
|
952
1249
|
alias :member_scope :path
|
953
1250
|
alias :nested_scope :path
|
1251
|
+
|
1252
|
+
def singleton?; true; end
|
954
1253
|
end
|
955
1254
|
|
956
1255
|
def resources_path_names(options)
|
@@ -963,21 +1262,21 @@ module ActionDispatch
|
|
963
1262
|
# a singular resource to map /profile (rather than /profile/:id) to
|
964
1263
|
# the show action:
|
965
1264
|
#
|
966
|
-
# resource :
|
1265
|
+
# resource :profile
|
967
1266
|
#
|
968
|
-
# creates six different routes in your application, all mapping to
|
969
|
-
# the +
|
1267
|
+
# This creates six different routes in your application, all mapping to
|
1268
|
+
# the +Profiles+ controller (note that the controller is named after
|
970
1269
|
# the plural):
|
971
1270
|
#
|
972
|
-
# GET
|
973
|
-
#
|
974
|
-
# GET
|
975
|
-
#
|
976
|
-
#
|
977
|
-
#
|
1271
|
+
# GET /profile/new
|
1272
|
+
# GET /profile
|
1273
|
+
# GET /profile/edit
|
1274
|
+
# PATCH/PUT /profile
|
1275
|
+
# DELETE /profile
|
1276
|
+
# POST /profile
|
978
1277
|
#
|
979
1278
|
# === Options
|
980
|
-
# Takes same options as
|
1279
|
+
# Takes same options as resources[rdoc-ref:#resources]
|
981
1280
|
def resource(*resources, &block)
|
982
1281
|
options = resources.extract_options!.dup
|
983
1282
|
|
@@ -985,22 +1284,22 @@ module ActionDispatch
|
|
985
1284
|
return self
|
986
1285
|
end
|
987
1286
|
|
988
|
-
|
989
|
-
|
1287
|
+
with_scope_level(:resource) do
|
1288
|
+
options = apply_action_options options
|
1289
|
+
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1290
|
+
yield if block_given?
|
990
1291
|
|
991
|
-
|
992
|
-
post :create
|
993
|
-
end if parent_resource.actions.include?(:create)
|
1292
|
+
concerns(options[:concerns]) if options[:concerns]
|
994
1293
|
|
995
|
-
|
996
|
-
|
997
|
-
|
1294
|
+
new do
|
1295
|
+
get :new
|
1296
|
+
end if parent_resource.actions.include?(:new)
|
998
1297
|
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1298
|
+
set_member_mappings_for_resource
|
1299
|
+
|
1300
|
+
collection do
|
1301
|
+
post :create
|
1302
|
+
end if parent_resource.actions.include?(:create)
|
1004
1303
|
end
|
1005
1304
|
end
|
1006
1305
|
|
@@ -1017,13 +1316,13 @@ module ActionDispatch
|
|
1017
1316
|
# creates seven different routes in your application, all mapping to
|
1018
1317
|
# the +Photos+ controller:
|
1019
1318
|
#
|
1020
|
-
# GET
|
1021
|
-
# GET
|
1022
|
-
# POST
|
1023
|
-
# GET
|
1024
|
-
# GET
|
1025
|
-
# PUT
|
1026
|
-
# DELETE
|
1319
|
+
# GET /photos
|
1320
|
+
# GET /photos/new
|
1321
|
+
# POST /photos
|
1322
|
+
# GET /photos/:id
|
1323
|
+
# GET /photos/:id/edit
|
1324
|
+
# PATCH/PUT /photos/:id
|
1325
|
+
# DELETE /photos/:id
|
1027
1326
|
#
|
1028
1327
|
# Resources can also be nested infinitely by using this block syntax:
|
1029
1328
|
#
|
@@ -1033,58 +1332,58 @@ module ActionDispatch
|
|
1033
1332
|
#
|
1034
1333
|
# This generates the following comments routes:
|
1035
1334
|
#
|
1036
|
-
# GET
|
1037
|
-
# GET
|
1038
|
-
# POST
|
1039
|
-
# GET
|
1040
|
-
# GET
|
1041
|
-
# PUT
|
1042
|
-
# DELETE
|
1335
|
+
# GET /photos/:photo_id/comments
|
1336
|
+
# GET /photos/:photo_id/comments/new
|
1337
|
+
# POST /photos/:photo_id/comments
|
1338
|
+
# GET /photos/:photo_id/comments/:id
|
1339
|
+
# GET /photos/:photo_id/comments/:id/edit
|
1340
|
+
# PATCH/PUT /photos/:photo_id/comments/:id
|
1341
|
+
# DELETE /photos/:photo_id/comments/:id
|
1043
1342
|
#
|
1044
1343
|
# === Options
|
1045
|
-
# Takes same options as
|
1344
|
+
# Takes same options as match[rdoc-ref:Base#match] as well as:
|
1046
1345
|
#
|
1047
1346
|
# [:path_names]
|
1048
1347
|
# Allows you to change the segment component of the +edit+ and +new+ actions.
|
1049
1348
|
# Actions not specified are not changed.
|
1050
1349
|
#
|
1051
|
-
# resources :posts, :
|
1350
|
+
# resources :posts, path_names: { new: "brand_new" }
|
1052
1351
|
#
|
1053
|
-
# The above example will now change /posts/new to /posts/brand_new
|
1352
|
+
# The above example will now change /posts/new to /posts/brand_new.
|
1054
1353
|
#
|
1055
1354
|
# [:path]
|
1056
1355
|
# Allows you to change the path prefix for the resource.
|
1057
1356
|
#
|
1058
|
-
# resources :posts, :
|
1357
|
+
# resources :posts, path: 'postings'
|
1059
1358
|
#
|
1060
|
-
# The resource and all segments will now route to /postings instead of /posts
|
1359
|
+
# The resource and all segments will now route to /postings instead of /posts.
|
1061
1360
|
#
|
1062
1361
|
# [:only]
|
1063
1362
|
# Only generate routes for the given actions.
|
1064
1363
|
#
|
1065
|
-
# resources :cows, :
|
1066
|
-
# resources :cows, :
|
1364
|
+
# resources :cows, only: :show
|
1365
|
+
# resources :cows, only: [:show, :index]
|
1067
1366
|
#
|
1068
1367
|
# [:except]
|
1069
1368
|
# Generate all routes except for the given actions.
|
1070
1369
|
#
|
1071
|
-
# resources :cows, :
|
1072
|
-
# resources :cows, :
|
1370
|
+
# resources :cows, except: :show
|
1371
|
+
# resources :cows, except: [:show, :index]
|
1073
1372
|
#
|
1074
1373
|
# [:shallow]
|
1075
1374
|
# Generates shallow routes for nested resource(s). When placed on a parent resource,
|
1076
1375
|
# generates shallow routes for all nested resources.
|
1077
1376
|
#
|
1078
|
-
# resources :posts, :
|
1377
|
+
# resources :posts, shallow: true do
|
1079
1378
|
# resources :comments
|
1080
1379
|
# end
|
1081
1380
|
#
|
1082
1381
|
# Is the same as:
|
1083
1382
|
#
|
1084
1383
|
# resources :posts do
|
1085
|
-
# resources :comments, :
|
1384
|
+
# resources :comments, except: [:show, :edit, :update, :destroy]
|
1086
1385
|
# end
|
1087
|
-
# resources :comments, :
|
1386
|
+
# resources :comments, only: [:show, :edit, :update, :destroy]
|
1088
1387
|
#
|
1089
1388
|
# This allows URLs for resources that otherwise would be deeply nested such
|
1090
1389
|
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
|
@@ -1093,29 +1392,52 @@ module ActionDispatch
|
|
1093
1392
|
# [:shallow_path]
|
1094
1393
|
# Prefixes nested shallow routes with the specified path.
|
1095
1394
|
#
|
1096
|
-
# scope :
|
1395
|
+
# scope shallow_path: "sekret" do
|
1097
1396
|
# resources :posts do
|
1098
|
-
# resources :comments, :
|
1397
|
+
# resources :comments, shallow: true
|
1099
1398
|
# end
|
1100
1399
|
# end
|
1101
1400
|
#
|
1102
1401
|
# The +comments+ resource here will have the following routes generated for it:
|
1103
1402
|
#
|
1104
|
-
# post_comments GET
|
1105
|
-
# post_comments POST
|
1106
|
-
# new_post_comment GET
|
1107
|
-
# edit_comment GET
|
1108
|
-
# comment GET
|
1109
|
-
# comment PUT
|
1110
|
-
# comment DELETE
|
1403
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1404
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1405
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1406
|
+
# edit_comment GET /sekret/comments/:id/edit(.:format)
|
1407
|
+
# comment GET /sekret/comments/:id(.:format)
|
1408
|
+
# comment PATCH/PUT /sekret/comments/:id(.:format)
|
1409
|
+
# comment DELETE /sekret/comments/:id(.:format)
|
1410
|
+
#
|
1411
|
+
# [:shallow_prefix]
|
1412
|
+
# Prefixes nested shallow route names with specified prefix.
|
1413
|
+
#
|
1414
|
+
# scope shallow_prefix: "sekret" do
|
1415
|
+
# resources :posts do
|
1416
|
+
# resources :comments, shallow: true
|
1417
|
+
# end
|
1418
|
+
# end
|
1419
|
+
#
|
1420
|
+
# The +comments+ resource here will have the following routes generated for it:
|
1421
|
+
#
|
1422
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1423
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1424
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1425
|
+
# edit_sekret_comment GET /comments/:id/edit(.:format)
|
1426
|
+
# sekret_comment GET /comments/:id(.:format)
|
1427
|
+
# sekret_comment PATCH/PUT /comments/:id(.:format)
|
1428
|
+
# sekret_comment DELETE /comments/:id(.:format)
|
1429
|
+
#
|
1430
|
+
# [:format]
|
1431
|
+
# Allows you to specify the default value for optional +format+
|
1432
|
+
# segment or disable it by supplying +false+.
|
1111
1433
|
#
|
1112
1434
|
# === Examples
|
1113
1435
|
#
|
1114
1436
|
# # routes call <tt>Admin::PostsController</tt>
|
1115
|
-
# resources :posts, :
|
1437
|
+
# resources :posts, module: "admin"
|
1116
1438
|
#
|
1117
1439
|
# # resource actions are at /admin/posts.
|
1118
|
-
# resources :posts, :
|
1440
|
+
# resources :posts, path: "admin/posts"
|
1119
1441
|
def resources(*resources, &block)
|
1120
1442
|
options = resources.extract_options!.dup
|
1121
1443
|
|
@@ -1123,23 +1445,23 @@ module ActionDispatch
|
|
1123
1445
|
return self
|
1124
1446
|
end
|
1125
1447
|
|
1126
|
-
|
1127
|
-
|
1448
|
+
with_scope_level(:resources) do
|
1449
|
+
options = apply_action_options options
|
1450
|
+
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1451
|
+
yield if block_given?
|
1128
1452
|
|
1129
|
-
|
1130
|
-
get :index if parent_resource.actions.include?(:index)
|
1131
|
-
post :create if parent_resource.actions.include?(:create)
|
1132
|
-
end
|
1453
|
+
concerns(options[:concerns]) if options[:concerns]
|
1133
1454
|
|
1134
|
-
|
1135
|
-
|
1136
|
-
|
1455
|
+
collection do
|
1456
|
+
get :index if parent_resource.actions.include?(:index)
|
1457
|
+
post :create if parent_resource.actions.include?(:create)
|
1458
|
+
end
|
1137
1459
|
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1460
|
+
new do
|
1461
|
+
get :new
|
1462
|
+
end if parent_resource.actions.include?(:new)
|
1463
|
+
|
1464
|
+
set_member_mappings_for_resource
|
1143
1465
|
end
|
1144
1466
|
end
|
1145
1467
|
|
@@ -1164,7 +1486,7 @@ module ActionDispatch
|
|
1164
1486
|
end
|
1165
1487
|
|
1166
1488
|
with_scope_level(:collection) do
|
1167
|
-
|
1489
|
+
path_scope(parent_resource.collection_scope) do
|
1168
1490
|
yield
|
1169
1491
|
end
|
1170
1492
|
end
|
@@ -1187,8 +1509,12 @@ module ActionDispatch
|
|
1187
1509
|
end
|
1188
1510
|
|
1189
1511
|
with_scope_level(:member) do
|
1190
|
-
|
1191
|
-
|
1512
|
+
if shallow?
|
1513
|
+
shallow_scope {
|
1514
|
+
path_scope(parent_resource.member_scope) { yield }
|
1515
|
+
}
|
1516
|
+
else
|
1517
|
+
path_scope(parent_resource.member_scope) { yield }
|
1192
1518
|
end
|
1193
1519
|
end
|
1194
1520
|
end
|
@@ -1199,7 +1525,7 @@ module ActionDispatch
|
|
1199
1525
|
end
|
1200
1526
|
|
1201
1527
|
with_scope_level(:new) do
|
1202
|
-
|
1528
|
+
path_scope(parent_resource.new_scope(action_path(:new))) do
|
1203
1529
|
yield
|
1204
1530
|
end
|
1205
1531
|
end
|
@@ -1211,23 +1537,21 @@ module ActionDispatch
|
|
1211
1537
|
end
|
1212
1538
|
|
1213
1539
|
with_scope_level(:nested) do
|
1214
|
-
if shallow?
|
1215
|
-
|
1216
|
-
|
1217
|
-
scope(
|
1218
|
-
else
|
1219
|
-
scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
|
1220
|
-
scope(parent_resource.nested_scope, nested_options) { yield }
|
1221
|
-
end
|
1540
|
+
if shallow? && shallow_nesting_depth >= 1
|
1541
|
+
shallow_scope do
|
1542
|
+
path_scope(parent_resource.nested_scope) do
|
1543
|
+
scope(nested_options) { yield }
|
1222
1544
|
end
|
1223
1545
|
end
|
1224
1546
|
else
|
1225
|
-
|
1547
|
+
path_scope(parent_resource.nested_scope) do
|
1548
|
+
scope(nested_options) { yield }
|
1549
|
+
end
|
1226
1550
|
end
|
1227
1551
|
end
|
1228
1552
|
end
|
1229
1553
|
|
1230
|
-
# See ActionDispatch::Routing::Mapper::Scoping#namespace
|
1554
|
+
# See ActionDispatch::Routing::Mapper::Scoping#namespace.
|
1231
1555
|
def namespace(path, options = {})
|
1232
1556
|
if resource_scope?
|
1233
1557
|
nested { super }
|
@@ -1237,20 +1561,42 @@ module ActionDispatch
|
|
1237
1561
|
end
|
1238
1562
|
|
1239
1563
|
def shallow
|
1240
|
-
scope
|
1241
|
-
|
1242
|
-
|
1564
|
+
@scope = @scope.new(shallow: true)
|
1565
|
+
yield
|
1566
|
+
ensure
|
1567
|
+
@scope = @scope.parent
|
1243
1568
|
end
|
1244
1569
|
|
1245
1570
|
def shallow?
|
1246
|
-
parent_resource.
|
1571
|
+
!parent_resource.singleton? && @scope[:shallow]
|
1247
1572
|
end
|
1248
1573
|
|
1249
|
-
|
1574
|
+
# Matches a URL pattern to one or more routes.
|
1575
|
+
# For more information, see match[rdoc-ref:Base#match].
|
1576
|
+
#
|
1577
|
+
# match 'path' => 'controller#action', via: :patch
|
1578
|
+
# match 'path', to: 'controller#action', via: :post
|
1579
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1580
|
+
def match(path, *rest, &block)
|
1250
1581
|
if rest.empty? && Hash === path
|
1251
1582
|
options = path
|
1252
|
-
path, to = options.find { |name,
|
1253
|
-
|
1583
|
+
path, to = options.find { |name, _value| name.is_a?(String) }
|
1584
|
+
|
1585
|
+
raise ArgumentError, "Route path not specified" if path.nil?
|
1586
|
+
|
1587
|
+
case to
|
1588
|
+
when Symbol
|
1589
|
+
options[:action] = to
|
1590
|
+
when String
|
1591
|
+
if to =~ /#/
|
1592
|
+
options[:to] = to
|
1593
|
+
else
|
1594
|
+
options[:controller] = to
|
1595
|
+
end
|
1596
|
+
else
|
1597
|
+
options[:to] = to
|
1598
|
+
end
|
1599
|
+
|
1254
1600
|
options.delete(path)
|
1255
1601
|
paths = [path]
|
1256
1602
|
else
|
@@ -1258,89 +1604,65 @@ module ActionDispatch
|
|
1258
1604
|
paths = [path] + rest
|
1259
1605
|
end
|
1260
1606
|
|
1261
|
-
if
|
1262
|
-
options
|
1263
|
-
end
|
1264
|
-
|
1265
|
-
path_without_format = path.to_s.sub(/\(\.:format\)$/, '')
|
1266
|
-
if using_match_shorthand?(path_without_format, options)
|
1267
|
-
options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
1268
|
-
end
|
1269
|
-
|
1270
|
-
options[:anchor] = true unless options.key?(:anchor)
|
1271
|
-
|
1272
|
-
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1273
|
-
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1274
|
-
end
|
1275
|
-
|
1276
|
-
paths.each { |_path| decomposed_match(_path, options.dup) }
|
1277
|
-
self
|
1278
|
-
end
|
1279
|
-
|
1280
|
-
def using_match_shorthand?(path, options)
|
1281
|
-
path && (options[:to] || options[:action]).nil? && path =~ %r{/[\w/]+$}
|
1282
|
-
end
|
1283
|
-
|
1284
|
-
def decomposed_match(path, options) # :nodoc:
|
1285
|
-
if on = options.delete(:on)
|
1286
|
-
send(on) { decomposed_match(path, options) }
|
1607
|
+
if options.key?(:defaults)
|
1608
|
+
defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
|
1287
1609
|
else
|
1288
|
-
|
1289
|
-
when :resources
|
1290
|
-
nested { decomposed_match(path, options) }
|
1291
|
-
when :resource
|
1292
|
-
member { decomposed_match(path, options) }
|
1293
|
-
else
|
1294
|
-
add_route(path, options)
|
1295
|
-
end
|
1610
|
+
map_match(paths, options, &block)
|
1296
1611
|
end
|
1297
1612
|
end
|
1298
1613
|
|
1299
|
-
|
1300
|
-
|
1301
|
-
|
1302
|
-
|
1303
|
-
|
1304
|
-
|
1305
|
-
|
1306
|
-
|
1307
|
-
|
1308
|
-
|
1309
|
-
|
1310
|
-
|
1614
|
+
# You can specify what Rails should route "/" to with the root method:
|
1615
|
+
#
|
1616
|
+
# root to: 'pages#main'
|
1617
|
+
#
|
1618
|
+
# For options, see +match+, as +root+ uses it internally.
|
1619
|
+
#
|
1620
|
+
# You can also pass a string which will expand
|
1621
|
+
#
|
1622
|
+
# root 'pages#main'
|
1623
|
+
#
|
1624
|
+
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
1625
|
+
# because this means it will be matched first. As this is the most popular route
|
1626
|
+
# of most Rails applications, this is beneficial.
|
1627
|
+
def root(path, options = {})
|
1628
|
+
if path.is_a?(String)
|
1629
|
+
options[:to] = path
|
1630
|
+
elsif path.is_a?(Hash) && options.empty?
|
1631
|
+
options = path
|
1311
1632
|
else
|
1312
|
-
|
1633
|
+
raise ArgumentError, "must be called with a path and/or options"
|
1313
1634
|
end
|
1314
1635
|
|
1315
|
-
|
1316
|
-
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
1317
|
-
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1318
|
-
end
|
1319
|
-
|
1320
|
-
def root(options={})
|
1321
|
-
if @scope[:scope_level] == :resources
|
1636
|
+
if @scope.resources?
|
1322
1637
|
with_scope_level(:root) do
|
1323
|
-
|
1324
|
-
|
1638
|
+
path_scope(parent_resource.path) do
|
1639
|
+
match_root_route(options)
|
1325
1640
|
end
|
1326
1641
|
end
|
1327
1642
|
else
|
1328
|
-
|
1643
|
+
match_root_route(options)
|
1329
1644
|
end
|
1330
1645
|
end
|
1331
1646
|
|
1332
|
-
|
1647
|
+
private
|
1333
1648
|
|
1334
|
-
def parent_resource
|
1649
|
+
def parent_resource
|
1335
1650
|
@scope[:scope_level_resource]
|
1336
1651
|
end
|
1337
1652
|
|
1338
|
-
def apply_common_behavior_for(method, resources, options, &block)
|
1653
|
+
def apply_common_behavior_for(method, resources, options, &block)
|
1339
1654
|
if resources.length > 1
|
1340
1655
|
resources.each { |r| send(method, r, options, &block) }
|
1341
1656
|
return true
|
1342
1657
|
end
|
1343
1658
|
|
1659
|
+
if options.delete(:shallow)
|
1660
|
+
shallow do
|
1661
|
+
send(method, resources.pop, options, &block)
|
1662
|
+
end
|
1663
|
+
return true
|
1664
|
+
end
|
1665
|
+
|
1344
1666
|
if resource_scope?
|
1345
1667
|
nested { send(method, resources.pop, options, &block) }
|
1346
1668
|
return true
|
@@ -1358,116 +1680,114 @@ module ActionDispatch
|
|
1358
1680
|
return true
|
1359
1681
|
end
|
1360
1682
|
|
1361
|
-
unless action_options?(options)
|
1362
|
-
options.merge!(scope_action_options) if scope_action_options?
|
1363
|
-
end
|
1364
|
-
|
1365
1683
|
false
|
1366
1684
|
end
|
1367
1685
|
|
1368
|
-
def
|
1369
|
-
options
|
1686
|
+
def apply_action_options(options)
|
1687
|
+
return options if action_options? options
|
1688
|
+
options.merge scope_action_options
|
1370
1689
|
end
|
1371
1690
|
|
1372
|
-
def
|
1373
|
-
|
1691
|
+
def action_options?(options)
|
1692
|
+
options[:only] || options[:except]
|
1374
1693
|
end
|
1375
1694
|
|
1376
|
-
def scope_action_options
|
1377
|
-
@scope[:
|
1695
|
+
def scope_action_options
|
1696
|
+
@scope[:action_options] || {}
|
1378
1697
|
end
|
1379
1698
|
|
1380
|
-
def resource_scope?
|
1381
|
-
|
1699
|
+
def resource_scope?
|
1700
|
+
@scope.resource_scope?
|
1382
1701
|
end
|
1383
1702
|
|
1384
|
-
def resource_method_scope?
|
1385
|
-
|
1703
|
+
def resource_method_scope?
|
1704
|
+
@scope.resource_method_scope?
|
1386
1705
|
end
|
1387
1706
|
|
1388
|
-
def
|
1389
|
-
|
1390
|
-
old_name_prefix, old_path = @scope[:as], @scope[:path]
|
1391
|
-
@scope[:as], @scope[:path] = nil, nil
|
1392
|
-
|
1393
|
-
with_scope_level(:exclusive) do
|
1394
|
-
yield
|
1395
|
-
end
|
1396
|
-
ensure
|
1397
|
-
@scope[:as], @scope[:path] = old_name_prefix, old_path
|
1398
|
-
end
|
1707
|
+
def nested_scope?
|
1708
|
+
@scope.nested?
|
1399
1709
|
end
|
1400
1710
|
|
1401
|
-
def with_scope_level(kind
|
1402
|
-
|
1403
|
-
old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
|
1711
|
+
def with_scope_level(kind) # :doc:
|
1712
|
+
@scope = @scope.new_level(kind)
|
1404
1713
|
yield
|
1405
1714
|
ensure
|
1406
|
-
@scope
|
1407
|
-
@scope[:scope_level_resource] = old_resource
|
1715
|
+
@scope = @scope.parent
|
1408
1716
|
end
|
1409
1717
|
|
1410
|
-
def resource_scope(
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
1718
|
+
def resource_scope(resource)
|
1719
|
+
@scope = @scope.new(scope_level_resource: resource)
|
1720
|
+
|
1721
|
+
controller(resource.resource_scope) { yield }
|
1722
|
+
ensure
|
1723
|
+
@scope = @scope.parent
|
1416
1724
|
end
|
1417
1725
|
|
1418
|
-
def nested_options
|
1419
|
-
options = { :
|
1726
|
+
def nested_options
|
1727
|
+
options = { as: parent_resource.member_name }
|
1420
1728
|
options[:constraints] = {
|
1421
|
-
|
1422
|
-
} if
|
1729
|
+
parent_resource.nested_param => param_constraint
|
1730
|
+
} if param_constraint?
|
1423
1731
|
|
1424
1732
|
options
|
1425
1733
|
end
|
1426
1734
|
|
1427
|
-
def
|
1428
|
-
@scope
|
1735
|
+
def shallow_nesting_depth
|
1736
|
+
@scope.find_all { |node|
|
1737
|
+
node.frame[:scope_level_resource]
|
1738
|
+
}.count { |node| node.frame[:scope_level_resource].shallow? }
|
1429
1739
|
end
|
1430
1740
|
|
1431
|
-
def
|
1432
|
-
@scope[:constraints][:
|
1741
|
+
def param_constraint?
|
1742
|
+
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
1433
1743
|
end
|
1434
1744
|
|
1435
|
-
def
|
1436
|
-
|
1745
|
+
def param_constraint
|
1746
|
+
@scope[:constraints][parent_resource.param]
|
1437
1747
|
end
|
1438
1748
|
|
1439
|
-
def
|
1440
|
-
|
1749
|
+
def canonical_action?(action)
|
1750
|
+
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
def shallow_scope
|
1754
|
+
scope = { as: @scope[:shallow_prefix],
|
1755
|
+
path: @scope[:shallow_path] }
|
1756
|
+
@scope = @scope.new scope
|
1757
|
+
|
1758
|
+
yield
|
1759
|
+
ensure
|
1760
|
+
@scope = @scope.parent
|
1441
1761
|
end
|
1442
1762
|
|
1443
|
-
def path_for_action(action, path)
|
1444
|
-
|
1445
|
-
"#{@scope[:shallow_path]}/#{parent_resource.path}/:id" : @scope[:path]
|
1763
|
+
def path_for_action(action, path)
|
1764
|
+
return "#{@scope[:path]}/#{path}" if path
|
1446
1765
|
|
1447
|
-
|
1448
|
-
|
1766
|
+
if canonical_action?(action)
|
1767
|
+
@scope[:path].to_s
|
1449
1768
|
else
|
1450
|
-
"#{
|
1769
|
+
"#{@scope[:path]}/#{action_path(action)}"
|
1451
1770
|
end
|
1452
1771
|
end
|
1453
1772
|
|
1454
|
-
def action_path(name
|
1455
|
-
|
1456
|
-
name = name.to_sym if name.is_a?(String) && !name.empty?
|
1457
|
-
path || @scope[:path_names][name] || name.to_s
|
1773
|
+
def action_path(name)
|
1774
|
+
@scope[:path_names][name.to_sym] || name
|
1458
1775
|
end
|
1459
1776
|
|
1460
|
-
def prefix_name_for_action(as, action)
|
1777
|
+
def prefix_name_for_action(as, action)
|
1461
1778
|
if as
|
1462
|
-
as
|
1463
|
-
elsif !canonical_action?(action
|
1464
|
-
action
|
1779
|
+
prefix = as
|
1780
|
+
elsif !canonical_action?(action)
|
1781
|
+
prefix = action
|
1782
|
+
end
|
1783
|
+
|
1784
|
+
if prefix && prefix != "/" && !prefix.empty?
|
1785
|
+
Mapper.normalize_name prefix.to_s.tr("-", "_")
|
1465
1786
|
end
|
1466
1787
|
end
|
1467
1788
|
|
1468
|
-
def name_for_action(as, action)
|
1789
|
+
def name_for_action(as, action)
|
1469
1790
|
prefix = prefix_name_for_action(as, action)
|
1470
|
-
prefix = Mapper.normalize_name(prefix) if prefix
|
1471
1791
|
name_prefix = @scope[:as]
|
1472
1792
|
|
1473
1793
|
if parent_resource
|
@@ -1477,44 +1797,471 @@ module ActionDispatch
|
|
1477
1797
|
member_name = parent_resource.member_name
|
1478
1798
|
end
|
1479
1799
|
|
1480
|
-
|
1481
|
-
|
1482
|
-
[name_prefix, prefix]
|
1483
|
-
when :collection
|
1484
|
-
[prefix, name_prefix, collection_name]
|
1485
|
-
when :new
|
1486
|
-
[prefix, :new, name_prefix, member_name]
|
1487
|
-
when :member
|
1488
|
-
[prefix, shallow_scoping? ? @scope[:shallow_prefix] : name_prefix, member_name]
|
1489
|
-
when :root
|
1490
|
-
[name_prefix, collection_name, prefix]
|
1491
|
-
else
|
1492
|
-
[name_prefix, member_name, prefix]
|
1493
|
-
end
|
1800
|
+
action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1801
|
+
candidate = action_name.select(&:present?).join("_")
|
1494
1802
|
|
1495
|
-
|
1803
|
+
unless candidate.empty?
|
1496
1804
|
# If a name was not explicitly given, we check if it is valid
|
1497
1805
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1498
1806
|
# forward so the underlying router engine treats it and raises an exception.
|
1499
1807
|
if as.nil?
|
1500
|
-
candidate unless
|
1808
|
+
candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
|
1501
1809
|
else
|
1502
1810
|
candidate
|
1503
1811
|
end
|
1504
1812
|
end
|
1505
1813
|
end
|
1814
|
+
|
1815
|
+
def set_member_mappings_for_resource # :doc:
|
1816
|
+
member do
|
1817
|
+
get :edit if parent_resource.actions.include?(:edit)
|
1818
|
+
get :show if parent_resource.actions.include?(:show)
|
1819
|
+
if parent_resource.actions.include?(:update)
|
1820
|
+
patch :update
|
1821
|
+
put :update
|
1822
|
+
end
|
1823
|
+
delete :destroy if parent_resource.actions.include?(:destroy)
|
1824
|
+
end
|
1825
|
+
end
|
1826
|
+
|
1827
|
+
def api_only? # :doc:
|
1828
|
+
@set.api_only?
|
1829
|
+
end
|
1830
|
+
|
1831
|
+
def path_scope(path)
|
1832
|
+
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
|
1833
|
+
yield
|
1834
|
+
ensure
|
1835
|
+
@scope = @scope.parent
|
1836
|
+
end
|
1837
|
+
|
1838
|
+
def map_match(paths, options)
|
1839
|
+
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1840
|
+
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1841
|
+
end
|
1842
|
+
|
1843
|
+
if @scope[:to]
|
1844
|
+
options[:to] ||= @scope[:to]
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
if @scope[:controller] && @scope[:action]
|
1848
|
+
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1849
|
+
end
|
1850
|
+
|
1851
|
+
controller = options.delete(:controller) || @scope[:controller]
|
1852
|
+
option_path = options.delete :path
|
1853
|
+
to = options.delete :to
|
1854
|
+
via = Mapping.check_via Array(options.delete(:via) {
|
1855
|
+
@scope[:via]
|
1856
|
+
})
|
1857
|
+
formatted = options.delete(:format) { @scope[:format] }
|
1858
|
+
anchor = options.delete(:anchor) { true }
|
1859
|
+
options_constraints = options.delete(:constraints) || {}
|
1860
|
+
|
1861
|
+
path_types = paths.group_by(&:class)
|
1862
|
+
path_types.fetch(String, []).each do |_path|
|
1863
|
+
route_options = options.dup
|
1864
|
+
if _path && option_path
|
1865
|
+
raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
|
1866
|
+
end
|
1867
|
+
to = get_to_from_path(_path, to, route_options[:action])
|
1868
|
+
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1869
|
+
end
|
1870
|
+
|
1871
|
+
path_types.fetch(Symbol, []).each do |action|
|
1872
|
+
route_options = options.dup
|
1873
|
+
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1874
|
+
end
|
1875
|
+
|
1876
|
+
self
|
1877
|
+
end
|
1878
|
+
|
1879
|
+
def get_to_from_path(path, to, action)
|
1880
|
+
return to if to || action
|
1881
|
+
|
1882
|
+
path_without_format = path.sub(/\(\.:format\)$/, "")
|
1883
|
+
if using_match_shorthand?(path_without_format)
|
1884
|
+
path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1885
|
+
else
|
1886
|
+
nil
|
1887
|
+
end
|
1888
|
+
end
|
1889
|
+
|
1890
|
+
def using_match_shorthand?(path)
|
1891
|
+
path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1892
|
+
end
|
1893
|
+
|
1894
|
+
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1895
|
+
if on = options.delete(:on)
|
1896
|
+
send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1897
|
+
else
|
1898
|
+
case @scope.scope_level
|
1899
|
+
when :resources
|
1900
|
+
nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1901
|
+
when :resource
|
1902
|
+
member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1903
|
+
else
|
1904
|
+
add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1905
|
+
end
|
1906
|
+
end
|
1907
|
+
end
|
1908
|
+
|
1909
|
+
def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1910
|
+
path = path_for_action(action, _path)
|
1911
|
+
raise ArgumentError, "path is required" if path.blank?
|
1912
|
+
|
1913
|
+
action = action.to_s
|
1914
|
+
|
1915
|
+
default_action = options.delete(:action) || @scope[:action]
|
1916
|
+
|
1917
|
+
if action =~ /^[\w\-\/]+$/
|
1918
|
+
default_action ||= action.tr("-", "_") unless action.include?("/")
|
1919
|
+
else
|
1920
|
+
action = nil
|
1921
|
+
end
|
1922
|
+
|
1923
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1924
|
+
options.delete(:as)
|
1925
|
+
else
|
1926
|
+
name_for_action(options.delete(:as), action)
|
1927
|
+
end
|
1928
|
+
|
1929
|
+
path = Mapping.normalize_path URI.parser.escape(path), formatted
|
1930
|
+
ast = Journey::Parser.parse path
|
1931
|
+
|
1932
|
+
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
1933
|
+
@set.add_route(mapping, as)
|
1934
|
+
end
|
1935
|
+
|
1936
|
+
def match_root_route(options)
|
1937
|
+
name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
|
1938
|
+
args = ["/", { as: name, via: :get }.merge!(options)]
|
1939
|
+
|
1940
|
+
match(*args)
|
1941
|
+
end
|
1942
|
+
end
|
1943
|
+
|
1944
|
+
# Routing Concerns allow you to declare common routes that can be reused
|
1945
|
+
# inside others resources and routes.
|
1946
|
+
#
|
1947
|
+
# concern :commentable do
|
1948
|
+
# resources :comments
|
1949
|
+
# end
|
1950
|
+
#
|
1951
|
+
# concern :image_attachable do
|
1952
|
+
# resources :images, only: :index
|
1953
|
+
# end
|
1954
|
+
#
|
1955
|
+
# These concerns are used in Resources routing:
|
1956
|
+
#
|
1957
|
+
# resources :messages, concerns: [:commentable, :image_attachable]
|
1958
|
+
#
|
1959
|
+
# or in a scope or namespace:
|
1960
|
+
#
|
1961
|
+
# namespace :posts do
|
1962
|
+
# concerns :commentable
|
1963
|
+
# end
|
1964
|
+
module Concerns
|
1965
|
+
# Define a routing concern using a name.
|
1966
|
+
#
|
1967
|
+
# Concerns may be defined inline, using a block, or handled by
|
1968
|
+
# another object, by passing that object as the second parameter.
|
1969
|
+
#
|
1970
|
+
# The concern object, if supplied, should respond to <tt>call</tt>,
|
1971
|
+
# which will receive two parameters:
|
1972
|
+
#
|
1973
|
+
# * The current mapper
|
1974
|
+
# * A hash of options which the concern object may use
|
1975
|
+
#
|
1976
|
+
# Options may also be used by concerns defined in a block by accepting
|
1977
|
+
# a block parameter. So, using a block, you might do something as
|
1978
|
+
# simple as limit the actions available on certain resources, passing
|
1979
|
+
# standard resource options through the concern:
|
1980
|
+
#
|
1981
|
+
# concern :commentable do |options|
|
1982
|
+
# resources :comments, options
|
1983
|
+
# end
|
1984
|
+
#
|
1985
|
+
# resources :posts, concerns: :commentable
|
1986
|
+
# resources :archived_posts do
|
1987
|
+
# # Don't allow comments on archived posts
|
1988
|
+
# concerns :commentable, only: [:index, :show]
|
1989
|
+
# end
|
1990
|
+
#
|
1991
|
+
# Or, using a callable object, you might implement something more
|
1992
|
+
# specific to your application, which would be out of place in your
|
1993
|
+
# routes file.
|
1994
|
+
#
|
1995
|
+
# # purchasable.rb
|
1996
|
+
# class Purchasable
|
1997
|
+
# def initialize(defaults = {})
|
1998
|
+
# @defaults = defaults
|
1999
|
+
# end
|
2000
|
+
#
|
2001
|
+
# def call(mapper, options = {})
|
2002
|
+
# options = @defaults.merge(options)
|
2003
|
+
# mapper.resources :purchases
|
2004
|
+
# mapper.resources :receipts
|
2005
|
+
# mapper.resources :returns if options[:returnable]
|
2006
|
+
# end
|
2007
|
+
# end
|
2008
|
+
#
|
2009
|
+
# # routes.rb
|
2010
|
+
# concern :purchasable, Purchasable.new(returnable: true)
|
2011
|
+
#
|
2012
|
+
# resources :toys, concerns: :purchasable
|
2013
|
+
# resources :electronics, concerns: :purchasable
|
2014
|
+
# resources :pets do
|
2015
|
+
# concerns :purchasable, returnable: false
|
2016
|
+
# end
|
2017
|
+
#
|
2018
|
+
# Any routing helpers can be used inside a concern. If using a
|
2019
|
+
# callable, they're accessible from the Mapper that's passed to
|
2020
|
+
# <tt>call</tt>.
|
2021
|
+
def concern(name, callable = nil, &block)
|
2022
|
+
callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
|
2023
|
+
@concerns[name] = callable
|
2024
|
+
end
|
2025
|
+
|
2026
|
+
# Use the named concerns
|
2027
|
+
#
|
2028
|
+
# resources :posts do
|
2029
|
+
# concerns :commentable
|
2030
|
+
# end
|
2031
|
+
#
|
2032
|
+
# Concerns also work in any routes helper that you want to use:
|
2033
|
+
#
|
2034
|
+
# namespace :posts do
|
2035
|
+
# concerns :commentable
|
2036
|
+
# end
|
2037
|
+
def concerns(*args)
|
2038
|
+
options = args.extract_options!
|
2039
|
+
args.flatten.each do |name|
|
2040
|
+
if concern = @concerns[name]
|
2041
|
+
concern.call(self, options)
|
2042
|
+
else
|
2043
|
+
raise ArgumentError, "No concern named #{name} was found!"
|
2044
|
+
end
|
2045
|
+
end
|
2046
|
+
end
|
2047
|
+
end
|
2048
|
+
|
2049
|
+
module CustomUrls
|
2050
|
+
# Define custom URL helpers that will be added to the application's
|
2051
|
+
# routes. This allows you to override and/or replace the default behavior
|
2052
|
+
# of routing helpers, e.g:
|
2053
|
+
#
|
2054
|
+
# direct :homepage do
|
2055
|
+
# "http://www.rubyonrails.org"
|
2056
|
+
# end
|
2057
|
+
#
|
2058
|
+
# direct :commentable do |model|
|
2059
|
+
# [ model, anchor: model.dom_id ]
|
2060
|
+
# end
|
2061
|
+
#
|
2062
|
+
# direct :main do
|
2063
|
+
# { controller: "pages", action: "index", subdomain: "www" }
|
2064
|
+
# end
|
2065
|
+
#
|
2066
|
+
# The return value from the block passed to +direct+ must be a valid set of
|
2067
|
+
# arguments for +url_for+ which will actually build the URL string. This can
|
2068
|
+
# be one of the following:
|
2069
|
+
#
|
2070
|
+
# * A string, which is treated as a generated URL
|
2071
|
+
# * A hash, e.g. <tt>{ controller: "pages", action: "index" }</tt>
|
2072
|
+
# * An array, which is passed to +polymorphic_url+
|
2073
|
+
# * An Active Model instance
|
2074
|
+
# * An Active Model class
|
2075
|
+
#
|
2076
|
+
# NOTE: Other URL helpers can be called in the block but be careful not to invoke
|
2077
|
+
# your custom URL helper again otherwise it will result in a stack overflow error.
|
2078
|
+
#
|
2079
|
+
# You can also specify default options that will be passed through to
|
2080
|
+
# your URL helper definition, e.g:
|
2081
|
+
#
|
2082
|
+
# direct :browse, page: 1, size: 10 do |options|
|
2083
|
+
# [ :products, options.merge(params.permit(:page, :size).to_h.symbolize_keys) ]
|
2084
|
+
# end
|
2085
|
+
#
|
2086
|
+
# In this instance the +params+ object comes from the context in which the
|
2087
|
+
# block is executed, e.g. generating a URL inside a controller action or a view.
|
2088
|
+
# If the block is executed where there isn't a +params+ object such as this:
|
2089
|
+
#
|
2090
|
+
# Rails.application.routes.url_helpers.browse_path
|
2091
|
+
#
|
2092
|
+
# then it will raise a +NameError+. Because of this you need to be aware of the
|
2093
|
+
# context in which you will use your custom URL helper when defining it.
|
2094
|
+
#
|
2095
|
+
# NOTE: The +direct+ method can't be used inside of a scope block such as
|
2096
|
+
# +namespace+ or +scope+ and will raise an error if it detects that it is.
|
2097
|
+
def direct(name, options = {}, &block)
|
2098
|
+
unless @scope.root?
|
2099
|
+
raise RuntimeError, "The direct method can't be used inside a routes scope block"
|
2100
|
+
end
|
2101
|
+
|
2102
|
+
@set.add_url_helper(name, options, &block)
|
2103
|
+
end
|
2104
|
+
|
2105
|
+
# Define custom polymorphic mappings of models to URLs. This alters the
|
2106
|
+
# behavior of +polymorphic_url+ and consequently the behavior of
|
2107
|
+
# +link_to+ and +form_for+ when passed a model instance, e.g:
|
2108
|
+
#
|
2109
|
+
# resource :basket
|
2110
|
+
#
|
2111
|
+
# resolve "Basket" do
|
2112
|
+
# [:basket]
|
2113
|
+
# end
|
2114
|
+
#
|
2115
|
+
# This will now generate "/basket" when a +Basket+ instance is passed to
|
2116
|
+
# +link_to+ or +form_for+ instead of the standard "/baskets/:id".
|
2117
|
+
#
|
2118
|
+
# NOTE: This custom behavior only applies to simple polymorphic URLs where
|
2119
|
+
# a single model instance is passed and not more complicated forms, e.g:
|
2120
|
+
#
|
2121
|
+
# # config/routes.rb
|
2122
|
+
# resource :profile
|
2123
|
+
# namespace :admin do
|
2124
|
+
# resources :users
|
2125
|
+
# end
|
2126
|
+
#
|
2127
|
+
# resolve("User") { [:profile] }
|
2128
|
+
#
|
2129
|
+
# # app/views/application/_menu.html.erb
|
2130
|
+
# link_to "Profile", @current_user
|
2131
|
+
# link_to "Profile", [:admin, @current_user]
|
2132
|
+
#
|
2133
|
+
# The first +link_to+ will generate "/profile" but the second will generate
|
2134
|
+
# the standard polymorphic URL of "/admin/users/1".
|
2135
|
+
#
|
2136
|
+
# You can pass options to a polymorphic mapping - the arity for the block
|
2137
|
+
# needs to be two as the instance is passed as the first argument, e.g:
|
2138
|
+
#
|
2139
|
+
# resolve "Basket", anchor: "items" do |basket, options|
|
2140
|
+
# [:basket, options]
|
2141
|
+
# end
|
2142
|
+
#
|
2143
|
+
# This generates the URL "/basket#items" because when the last item in an
|
2144
|
+
# array passed to +polymorphic_url+ is a hash then it's treated as options
|
2145
|
+
# to the URL helper that gets called.
|
2146
|
+
#
|
2147
|
+
# NOTE: The +resolve+ method can't be used inside of a scope block such as
|
2148
|
+
# +namespace+ or +scope+ and will raise an error if it detects that it is.
|
2149
|
+
def resolve(*args, &block)
|
2150
|
+
unless @scope.root?
|
2151
|
+
raise RuntimeError, "The resolve method can't be used inside a routes scope block"
|
2152
|
+
end
|
2153
|
+
|
2154
|
+
options = args.extract_options!
|
2155
|
+
args = args.flatten(1)
|
2156
|
+
|
2157
|
+
args.each do |klass|
|
2158
|
+
@set.add_polymorphic_mapping(klass, options, &block)
|
2159
|
+
end
|
2160
|
+
end
|
2161
|
+
end
|
2162
|
+
|
2163
|
+
class Scope # :nodoc:
|
2164
|
+
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
2165
|
+
:controller, :action, :path_names, :constraints,
|
2166
|
+
:shallow, :blocks, :defaults, :via, :format, :options, :to]
|
2167
|
+
|
2168
|
+
RESOURCE_SCOPES = [:resource, :resources]
|
2169
|
+
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
2170
|
+
|
2171
|
+
attr_reader :parent, :scope_level
|
2172
|
+
|
2173
|
+
def initialize(hash, parent = NULL, scope_level = nil)
|
2174
|
+
@hash = hash
|
2175
|
+
@parent = parent
|
2176
|
+
@scope_level = scope_level
|
2177
|
+
end
|
2178
|
+
|
2179
|
+
def nested?
|
2180
|
+
scope_level == :nested
|
2181
|
+
end
|
2182
|
+
|
2183
|
+
def null?
|
2184
|
+
@hash.nil? && @parent.nil?
|
2185
|
+
end
|
2186
|
+
|
2187
|
+
def root?
|
2188
|
+
@parent.null?
|
2189
|
+
end
|
2190
|
+
|
2191
|
+
def resources?
|
2192
|
+
scope_level == :resources
|
2193
|
+
end
|
2194
|
+
|
2195
|
+
def resource_method_scope?
|
2196
|
+
RESOURCE_METHOD_SCOPES.include? scope_level
|
2197
|
+
end
|
2198
|
+
|
2199
|
+
def action_name(name_prefix, prefix, collection_name, member_name)
|
2200
|
+
case scope_level
|
2201
|
+
when :nested
|
2202
|
+
[name_prefix, prefix]
|
2203
|
+
when :collection
|
2204
|
+
[prefix, name_prefix, collection_name]
|
2205
|
+
when :new
|
2206
|
+
[prefix, :new, name_prefix, member_name]
|
2207
|
+
when :member
|
2208
|
+
[prefix, name_prefix, member_name]
|
2209
|
+
when :root
|
2210
|
+
[name_prefix, collection_name, prefix]
|
2211
|
+
else
|
2212
|
+
[name_prefix, member_name, prefix]
|
2213
|
+
end
|
2214
|
+
end
|
2215
|
+
|
2216
|
+
def resource_scope?
|
2217
|
+
RESOURCE_SCOPES.include? scope_level
|
2218
|
+
end
|
2219
|
+
|
2220
|
+
def options
|
2221
|
+
OPTIONS
|
2222
|
+
end
|
2223
|
+
|
2224
|
+
def new(hash)
|
2225
|
+
self.class.new hash, self, scope_level
|
2226
|
+
end
|
2227
|
+
|
2228
|
+
def new_level(level)
|
2229
|
+
self.class.new(frame, self, level)
|
2230
|
+
end
|
2231
|
+
|
2232
|
+
def [](key)
|
2233
|
+
scope = find { |node| node.frame.key? key }
|
2234
|
+
scope && scope.frame[key]
|
2235
|
+
end
|
2236
|
+
|
2237
|
+
include Enumerable
|
2238
|
+
|
2239
|
+
def each
|
2240
|
+
node = self
|
2241
|
+
until node.equal? NULL
|
2242
|
+
yield node
|
2243
|
+
node = node.parent
|
2244
|
+
end
|
2245
|
+
end
|
2246
|
+
|
2247
|
+
def frame; @hash; end
|
2248
|
+
|
2249
|
+
NULL = Scope.new(nil, nil)
|
1506
2250
|
end
|
1507
2251
|
|
1508
2252
|
def initialize(set) #:nodoc:
|
1509
2253
|
@set = set
|
1510
|
-
@scope =
|
2254
|
+
@scope = Scope.new(path_names: @set.resources_path_names)
|
2255
|
+
@concerns = {}
|
1511
2256
|
end
|
1512
2257
|
|
1513
2258
|
include Base
|
1514
2259
|
include HttpHelpers
|
1515
2260
|
include Redirection
|
1516
2261
|
include Scoping
|
2262
|
+
include Concerns
|
1517
2263
|
include Resources
|
2264
|
+
include CustomUrls
|
1518
2265
|
end
|
1519
2266
|
end
|
1520
2267
|
end
|