actionpack 3.2.19 → 4.2.11.3
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 +7 -0
- data/CHANGELOG.md +412 -503
- data/MIT-LICENSE +1 -1
- data/README.rdoc +11 -294
- data/lib/abstract_controller/asset_paths.rb +2 -2
- data/lib/abstract_controller/base.rb +52 -18
- data/lib/abstract_controller/callbacks.rb +87 -89
- data/lib/abstract_controller/collector.rb +17 -3
- data/lib/abstract_controller/helpers.rb +41 -14
- data/lib/abstract_controller/logger.rb +1 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +3 -3
- data/lib/abstract_controller/rendering.rb +65 -118
- data/lib/abstract_controller/translation.rb +16 -1
- data/lib/abstract_controller/url_for.rb +7 -7
- data/lib/abstract_controller.rb +2 -10
- data/lib/action_controller/base.rb +61 -28
- data/lib/action_controller/caching/fragments.rb +30 -54
- data/lib/action_controller/caching.rb +38 -35
- data/lib/action_controller/log_subscriber.rb +35 -18
- data/lib/action_controller/metal/conditional_get.rb +103 -34
- data/lib/action_controller/metal/data_streaming.rb +20 -26
- data/lib/action_controller/metal/etag_with_template_digest.rb +50 -0
- data/lib/action_controller/metal/exceptions.rb +19 -6
- data/lib/action_controller/metal/flash.rb +41 -9
- data/lib/action_controller/metal/force_ssl.rb +70 -12
- data/lib/action_controller/metal/head.rb +30 -7
- data/lib/action_controller/metal/helpers.rb +11 -11
- data/lib/action_controller/metal/hide_actions.rb +0 -1
- data/lib/action_controller/metal/http_authentication.rb +140 -94
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +11 -7
- data/lib/action_controller/metal/live.rb +328 -0
- data/lib/action_controller/metal/mime_responds.rb +161 -152
- data/lib/action_controller/metal/params_wrapper.rb +126 -81
- data/lib/action_controller/metal/rack_delegation.rb +10 -4
- data/lib/action_controller/metal/redirecting.rb +44 -41
- data/lib/action_controller/metal/renderers.rb +48 -19
- data/lib/action_controller/metal/rendering.rb +46 -11
- data/lib/action_controller/metal/request_forgery_protection.rb +250 -29
- data/lib/action_controller/metal/streaming.rb +30 -38
- data/lib/action_controller/metal/strong_parameters.rb +669 -0
- data/lib/action_controller/metal/testing.rb +12 -18
- data/lib/action_controller/metal/url_for.rb +31 -29
- data/lib/action_controller/metal.rb +31 -40
- data/lib/action_controller/model_naming.rb +12 -0
- data/lib/action_controller/railtie.rb +38 -18
- data/lib/action_controller/railties/helpers.rb +22 -0
- data/lib/action_controller/test_case.rb +359 -173
- data/lib/action_controller.rb +9 -16
- data/lib/action_dispatch/http/cache.rb +64 -11
- data/lib/action_dispatch/http/filter_parameters.rb +20 -10
- data/lib/action_dispatch/http/filter_redirect.rb +38 -0
- data/lib/action_dispatch/http/headers.rb +85 -17
- data/lib/action_dispatch/http/mime_negotiation.rb +55 -5
- data/lib/action_dispatch/http/mime_type.rb +167 -114
- data/lib/action_dispatch/http/mime_types.rb +2 -1
- data/lib/action_dispatch/http/parameter_filter.rb +44 -46
- data/lib/action_dispatch/http/parameters.rb +30 -46
- data/lib/action_dispatch/http/rack_cache.rb +2 -3
- data/lib/action_dispatch/http/request.rb +108 -45
- data/lib/action_dispatch/http/response.rb +247 -48
- data/lib/action_dispatch/http/upload.rb +60 -29
- data/lib/action_dispatch/http/url.rb +135 -45
- data/lib/action_dispatch/journey/backwards.rb +5 -0
- data/lib/action_dispatch/journey/formatter.rb +166 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +47 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +157 -0
- data/lib/action_dispatch/journey/nfa/builder.rb +76 -0
- data/lib/action_dispatch/journey/nfa/dot.rb +36 -0
- data/lib/action_dispatch/journey/nfa/simulator.rb +47 -0
- data/lib/action_dispatch/journey/nfa/transition_table.rb +163 -0
- data/lib/action_dispatch/journey/nodes/node.rb +128 -0
- data/lib/action_dispatch/journey/parser.rb +198 -0
- data/lib/action_dispatch/journey/parser.y +49 -0
- data/lib/action_dispatch/journey/parser_extras.rb +23 -0
- data/lib/action_dispatch/journey/path/pattern.rb +193 -0
- data/lib/action_dispatch/journey/route.rb +125 -0
- data/lib/action_dispatch/journey/router/strexp.rb +27 -0
- data/lib/action_dispatch/journey/router/utils.rb +93 -0
- data/lib/action_dispatch/journey/router.rb +144 -0
- data/lib/action_dispatch/journey/routes.rb +80 -0
- data/lib/action_dispatch/journey/scanner.rb +61 -0
- data/lib/action_dispatch/journey/visitors.rb +221 -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 +5 -0
- data/lib/action_dispatch/middleware/callbacks.rb +16 -11
- data/lib/action_dispatch/middleware/cookies.rb +346 -125
- data/lib/action_dispatch/middleware/debug_exceptions.rb +52 -24
- data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -9
- data/lib/action_dispatch/middleware/flash.rb +85 -72
- data/lib/action_dispatch/middleware/params_parser.rb +16 -31
- data/lib/action_dispatch/middleware/public_exceptions.rb +39 -14
- data/lib/action_dispatch/middleware/reloader.rb +16 -7
- data/lib/action_dispatch/middleware/remote_ip.rb +132 -40
- data/lib/action_dispatch/middleware/request_id.rb +3 -7
- data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
- data/lib/action_dispatch/middleware/session/cache_store.rb +3 -3
- data/lib/action_dispatch/middleware/session/cookie_store.rb +84 -29
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +15 -44
- data/lib/action_dispatch/middleware/ssl.rb +72 -0
- data/lib/action_dispatch/middleware/stack.rb +6 -1
- data/lib/action_dispatch/middleware/static.rb +80 -23
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +34 -0
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +23 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +27 -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/layout.erb +133 -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 +19 -6
- data/lib/action_dispatch/request/session.rb +193 -0
- data/lib/action_dispatch/request/utils.rb +35 -0
- data/lib/action_dispatch/routing/endpoint.rb +10 -0
- data/lib/action_dispatch/routing/inspector.rb +234 -0
- data/lib/action_dispatch/routing/mapper.rb +897 -436
- data/lib/action_dispatch/routing/polymorphic_routes.rb +213 -92
- data/lib/action_dispatch/routing/redirection.rb +97 -37
- data/lib/action_dispatch/routing/route_set.rb +432 -239
- data/lib/action_dispatch/routing/routes_proxy.rb +7 -4
- data/lib/action_dispatch/routing/url_for.rb +63 -34
- data/lib/action_dispatch/routing.rb +57 -89
- data/lib/action_dispatch/testing/assertions/dom.rb +2 -36
- data/lib/action_dispatch/testing/assertions/response.rb +24 -38
- data/lib/action_dispatch/testing/assertions/routing.rb +55 -54
- data/lib/action_dispatch/testing/assertions/selector.rb +2 -434
- data/lib/action_dispatch/testing/assertions/tag.rb +2 -137
- data/lib/action_dispatch/testing/assertions.rb +11 -7
- data/lib/action_dispatch/testing/integration.rb +88 -72
- data/lib/action_dispatch/testing/test_process.rb +9 -6
- data/lib/action_dispatch/testing/test_request.rb +13 -9
- data/lib/action_dispatch/testing/test_response.rb +1 -5
- data/lib/action_dispatch.rb +24 -21
- data/lib/action_pack/gem_version.rb +15 -0
- data/lib/action_pack/version.rb +5 -7
- data/lib/action_pack.rb +1 -1
- metadata +181 -292
- 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/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/responder.rb +0 -286
- data/lib/action_controller/metal/session_management.rb +0 -14
- 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/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/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 -90
- data/lib/action_view/helpers/sanitize_helper.rb +0 -259
- data/lib/action_view/helpers/tag_helper.rb +0 -160
- 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 -254
- data/lib/action_view/path_set.rb +0 -89
- 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 -54
- data/lib/action_view/renderer/streaming_template_renderer.rb +0 -106
- data/lib/action_view/renderer/template_renderer.rb +0 -94
- 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 -272
- 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 -245
- data/lib/action_view/testing/resolvers.rb +0 -50
- 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,232 +1,363 @@
|
|
1
1
|
require 'active_support/core_ext/hash/except'
|
2
|
-
require 'active_support/core_ext/
|
3
|
-
require 'active_support/core_ext/
|
2
|
+
require 'active_support/core_ext/hash/reverse_merge'
|
3
|
+
require 'active_support/core_ext/hash/slice'
|
4
4
|
require 'active_support/core_ext/enumerable'
|
5
|
+
require 'active_support/core_ext/array/extract_options'
|
6
|
+
require 'active_support/core_ext/module/remove_method'
|
7
|
+
require 'active_support/core_ext/string/filters'
|
5
8
|
require 'active_support/inflector'
|
6
9
|
require 'action_dispatch/routing/redirection'
|
10
|
+
require 'action_dispatch/routing/endpoint'
|
11
|
+
require 'active_support/deprecation'
|
7
12
|
|
8
13
|
module ActionDispatch
|
9
14
|
module Routing
|
10
15
|
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
|
16
|
+
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
19
17
|
|
18
|
+
class Constraints < Endpoint #:nodoc:
|
20
19
|
attr_reader :app, :constraints
|
21
20
|
|
22
|
-
def initialize(app, constraints,
|
23
|
-
|
24
|
-
|
21
|
+
def initialize(app, constraints, dispatcher_p)
|
22
|
+
# Unwrap Constraints objects. I don't actually think it's possible
|
23
|
+
# to pass a Constraints object to this constructor, but there were
|
24
|
+
# multiple places that kept testing children of this object. I
|
25
|
+
# *think* they were just being defensive, but I have no idea.
|
26
|
+
if app.is_a?(self.class)
|
27
|
+
constraints += app.constraints
|
28
|
+
app = app.app
|
29
|
+
end
|
25
30
|
|
26
|
-
|
27
|
-
req = @request.new(env)
|
31
|
+
@dispatcher = dispatcher_p
|
28
32
|
|
29
|
-
@constraints
|
30
|
-
|
31
|
-
return false
|
32
|
-
elsif constraint.respond_to?(:call) && !constraint.call(*constraint_args(constraint, req))
|
33
|
-
return false
|
34
|
-
end
|
35
|
-
}
|
33
|
+
@app, @constraints, = app, constraints
|
34
|
+
end
|
36
35
|
|
37
|
-
|
38
|
-
|
39
|
-
|
36
|
+
def dispatcher?; @dispatcher; 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
|
40
43
|
end
|
41
44
|
|
42
|
-
def
|
43
|
-
|
45
|
+
def serve(req)
|
46
|
+
return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
|
47
|
+
|
48
|
+
if dispatcher?
|
49
|
+
@app.serve req
|
50
|
+
else
|
51
|
+
@app.call req.env
|
52
|
+
end
|
44
53
|
end
|
45
54
|
|
46
55
|
private
|
47
56
|
def constraint_args(constraint, request)
|
48
|
-
constraint.arity == 1 ? [request] : [request.
|
57
|
+
constraint.arity == 1 ? [request] : [request.path_parameters, request]
|
49
58
|
end
|
50
59
|
end
|
51
60
|
|
52
61
|
class Mapping #:nodoc:
|
53
|
-
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
|
54
62
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
55
|
-
|
63
|
+
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
|
+
|
65
|
+
attr_reader :requirements, :conditions, :defaults
|
66
|
+
attr_reader :to, :default_controller, :default_action, :as, :anchor
|
67
|
+
|
68
|
+
def self.build(scope, set, path, as, options)
|
69
|
+
options = scope[:options].merge(options) if scope[:options]
|
70
|
+
|
71
|
+
options.delete :only
|
72
|
+
options.delete :except
|
73
|
+
options.delete :shallow_path
|
74
|
+
options.delete :shallow_prefix
|
75
|
+
options.delete :shallow
|
76
|
+
|
77
|
+
defaults = (scope[:defaults] || {}).merge options.delete(:defaults) || {}
|
78
|
+
|
79
|
+
new scope, set, path, defaults, as, options
|
80
|
+
end
|
81
|
+
|
82
|
+
def initialize(scope, set, path, defaults, as, options)
|
83
|
+
@requirements, @conditions = {}, {}
|
84
|
+
@defaults = defaults
|
85
|
+
@set = set
|
86
|
+
|
87
|
+
@to = options.delete :to
|
88
|
+
@default_controller = options.delete(:controller) || scope[:controller]
|
89
|
+
@default_action = options.delete(:action) || scope[:action]
|
90
|
+
@as = as
|
91
|
+
@anchor = options.delete :anchor
|
92
|
+
|
93
|
+
formatted = options.delete :format
|
94
|
+
via = Array(options.delete(:via) { [] })
|
95
|
+
options_constraints = options.delete :constraints
|
96
|
+
|
97
|
+
path = normalize_path! path, formatted
|
98
|
+
ast = path_ast path
|
99
|
+
path_params = path_params ast
|
100
|
+
|
101
|
+
options = normalize_options!(options, formatted, path_params, ast, scope[:module])
|
102
|
+
|
103
|
+
|
104
|
+
split_constraints(path_params, scope[:constraints]) if scope[:constraints]
|
105
|
+
constraints = constraints(options, path_params)
|
106
|
+
|
107
|
+
split_constraints path_params, constraints
|
108
|
+
|
109
|
+
@blocks = blocks(options_constraints, scope[:blocks])
|
56
110
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
111
|
+
if options_constraints.is_a?(Hash)
|
112
|
+
split_constraints path_params, options_constraints
|
113
|
+
options_constraints.each do |key, default|
|
114
|
+
if URL_OPTIONS.include?(key) && (String === default || Integer === default)
|
115
|
+
@defaults[key] ||= default
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
normalize_format!(formatted)
|
121
|
+
|
122
|
+
@conditions[:path_info] = path
|
123
|
+
@conditions[:parsed_path_info] = ast
|
124
|
+
|
125
|
+
add_request_method(via, @conditions)
|
126
|
+
normalize_defaults!(options)
|
62
127
|
end
|
63
128
|
|
64
129
|
def to_route
|
65
|
-
[ app, conditions, requirements, defaults,
|
130
|
+
[ app(@blocks), conditions, requirements, defaults, as, anchor ]
|
66
131
|
end
|
67
132
|
|
68
133
|
private
|
69
134
|
|
70
|
-
def
|
71
|
-
|
72
|
-
|
73
|
-
requirements.each do |name, requirement|
|
74
|
-
# segment_keys.include?(k.to_s) || k == :controller
|
75
|
-
next unless Regexp === requirement && !constraints[name]
|
135
|
+
def normalize_path!(path, format)
|
136
|
+
path = Mapper.normalize_path(path)
|
76
137
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
138
|
+
if format == true
|
139
|
+
"#{path}.:format"
|
140
|
+
elsif optional_format?(path, format)
|
141
|
+
"#{path}(.:format)"
|
142
|
+
else
|
143
|
+
path
|
83
144
|
end
|
84
145
|
end
|
85
146
|
|
86
|
-
|
87
|
-
|
88
|
-
path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
|
147
|
+
def optional_format?(path, format)
|
148
|
+
format != false && path !~ OPTIONAL_FORMAT_REGEX
|
89
149
|
end
|
90
150
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
151
|
+
def normalize_options!(options, formatted, path_params, path_ast, modyoule)
|
152
|
+
# Add a constraint for wildcard route to make it non-greedy and match the
|
153
|
+
# optional format part of the route by default
|
154
|
+
if formatted != false
|
155
|
+
path_ast.grep(Journey::Nodes::Star) do |node|
|
156
|
+
options[node.name.to_sym] ||= /.+?/
|
157
|
+
end
|
158
|
+
end
|
94
159
|
|
95
|
-
if
|
96
|
-
raise ArgumentError, ":controller segment is not allowed within a namespace block" if
|
160
|
+
if path_params.include?(:controller)
|
161
|
+
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
97
162
|
|
98
163
|
# Add a default constraint for :controller path segments that matches namespaced
|
99
164
|
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
100
165
|
# GET /admin/products/show/1
|
101
|
-
# => { :
|
102
|
-
|
166
|
+
# => { controller: 'admin/products', action: 'show', id: '1' }
|
167
|
+
options[:controller] ||= /.+?/
|
103
168
|
end
|
104
169
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
170
|
+
if to.respond_to? :call
|
171
|
+
options
|
172
|
+
else
|
173
|
+
to_endpoint = split_to to
|
174
|
+
controller = to_endpoint[0] || default_controller
|
175
|
+
action = to_endpoint[1] || default_action
|
176
|
+
|
177
|
+
controller = add_controller_module(controller, modyoule)
|
178
|
+
|
179
|
+
options.merge! check_controller_and_action(path_params, controller, action)
|
109
180
|
end
|
181
|
+
end
|
110
182
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
"#{path}(.:format)"
|
183
|
+
def split_constraints(path_params, constraints)
|
184
|
+
constraints.each_pair do |key, requirement|
|
185
|
+
if path_params.include?(key) || key == :controller
|
186
|
+
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
187
|
+
@requirements[key] = requirement
|
188
|
+
else
|
189
|
+
@conditions[key] = requirement
|
190
|
+
end
|
120
191
|
end
|
121
192
|
end
|
122
193
|
|
123
|
-
def
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
@
|
128
|
-
|
194
|
+
def normalize_format!(formatted)
|
195
|
+
if formatted == true
|
196
|
+
@requirements[:format] ||= /.+/
|
197
|
+
elsif Regexp === formatted
|
198
|
+
@requirements[:format] = formatted
|
199
|
+
@defaults[:format] = nil
|
200
|
+
elsif String === formatted
|
201
|
+
@requirements[:format] = Regexp.compile(formatted)
|
202
|
+
@defaults[:format] = formatted
|
203
|
+
end
|
129
204
|
end
|
130
205
|
|
131
|
-
def
|
132
|
-
|
206
|
+
def verify_regexp_requirement(requirement)
|
207
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
208
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
209
|
+
end
|
210
|
+
|
211
|
+
if requirement.multiline?
|
212
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
213
|
+
end
|
133
214
|
end
|
134
215
|
|
135
|
-
def
|
136
|
-
|
137
|
-
|
138
|
-
|
216
|
+
def normalize_defaults!(options)
|
217
|
+
options.each_pair do |key, default|
|
218
|
+
unless Regexp === default
|
219
|
+
@defaults[key] = default
|
220
|
+
end
|
139
221
|
end
|
140
222
|
end
|
141
223
|
|
142
|
-
def
|
143
|
-
|
144
|
-
|
145
|
-
@options.each { |k, v| defaults[k] = v unless v.is_a?(Regexp) || IGNORE_OPTIONS.include?(k.to_sym) }
|
224
|
+
def verify_callable_constraint(callable_constraint)
|
225
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
226
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
146
227
|
end
|
147
228
|
end
|
148
229
|
|
149
|
-
def
|
150
|
-
if
|
151
|
-
{ }
|
152
|
-
else
|
153
|
-
if to.is_a?(String)
|
154
|
-
controller, action = to.split('#')
|
155
|
-
elsif to.is_a?(Symbol)
|
156
|
-
action = to.to_s
|
157
|
-
end
|
230
|
+
def add_request_method(via, conditions)
|
231
|
+
return if via == [:all]
|
158
232
|
|
159
|
-
|
160
|
-
|
233
|
+
if via.empty?
|
234
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
235
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
236
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
237
|
+
" Instead of: match \"controller#action\"\n" \
|
238
|
+
" Do: get \"controller#action\""
|
239
|
+
raise ArgumentError, msg
|
240
|
+
end
|
161
241
|
|
162
|
-
|
163
|
-
|
164
|
-
end
|
242
|
+
conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
|
243
|
+
end
|
165
244
|
|
166
|
-
|
167
|
-
|
168
|
-
|
245
|
+
def app(blocks)
|
246
|
+
if to.respond_to?(:call)
|
247
|
+
Constraints.new(to, blocks, false)
|
248
|
+
elsif blocks.any?
|
249
|
+
Constraints.new(dispatcher(defaults), blocks, true)
|
250
|
+
else
|
251
|
+
dispatcher(defaults)
|
252
|
+
end
|
253
|
+
end
|
169
254
|
|
170
|
-
|
171
|
-
|
255
|
+
def check_controller_and_action(path_params, controller, action)
|
256
|
+
hash = check_part(:controller, controller, path_params, {}) do |part|
|
257
|
+
translate_controller(part) {
|
258
|
+
message = "'#{part}' is not a supported controller name. This can lead to potential routing problems."
|
259
|
+
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
172
260
|
|
173
|
-
|
174
|
-
|
175
|
-
|
261
|
+
raise ArgumentError, message
|
262
|
+
}
|
263
|
+
end
|
176
264
|
|
177
|
-
|
178
|
-
|
179
|
-
|
265
|
+
check_part(:action, action, path_params, hash) { |part|
|
266
|
+
part.is_a?(Regexp) ? part : part.to_s
|
267
|
+
}
|
268
|
+
end
|
180
269
|
|
181
|
-
|
182
|
-
|
183
|
-
hash[
|
184
|
-
|
270
|
+
def check_part(name, part, path_params, hash)
|
271
|
+
if part
|
272
|
+
hash[name] = yield(part)
|
273
|
+
else
|
274
|
+
unless path_params.include?(name)
|
275
|
+
message = "Missing :#{name} key on routes definition, please check your routes."
|
276
|
+
raise ArgumentError, message
|
277
|
+
end
|
278
|
+
end
|
279
|
+
hash
|
280
|
+
end
|
281
|
+
|
282
|
+
def split_to(to)
|
283
|
+
case to
|
284
|
+
when Symbol
|
285
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
286
|
+
Defining a route where `to` is a symbol is deprecated.
|
287
|
+
Please change `to: :#{to}` to `action: :#{to}`.
|
288
|
+
MSG
|
289
|
+
|
290
|
+
[nil, to.to_s]
|
291
|
+
when /#/ then to.split('#')
|
292
|
+
when String
|
293
|
+
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
294
|
+
Defining a route where `to` is a controller without an action is deprecated.
|
295
|
+
Please change `to: '#{to}'` to `controller: '#{to}'`.
|
296
|
+
MSG
|
297
|
+
|
298
|
+
[to, nil]
|
299
|
+
else
|
300
|
+
[]
|
185
301
|
end
|
186
302
|
end
|
187
303
|
|
188
|
-
def
|
189
|
-
|
190
|
-
|
191
|
-
|
304
|
+
def add_controller_module(controller, modyoule)
|
305
|
+
if modyoule && !controller.is_a?(Regexp)
|
306
|
+
if controller =~ %r{\A/}
|
307
|
+
controller[1..-1]
|
308
|
+
else
|
309
|
+
[modyoule, controller].compact.join("/")
|
310
|
+
end
|
192
311
|
else
|
193
|
-
|
312
|
+
controller
|
194
313
|
end
|
195
314
|
end
|
196
315
|
|
197
|
-
def
|
198
|
-
|
316
|
+
def translate_controller(controller)
|
317
|
+
return controller if Regexp === controller
|
318
|
+
return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
|
319
|
+
|
320
|
+
yield
|
199
321
|
end
|
200
322
|
|
201
|
-
def
|
202
|
-
if
|
203
|
-
|
204
|
-
|
323
|
+
def blocks(options_constraints, scope_blocks)
|
324
|
+
if options_constraints && !options_constraints.is_a?(Hash)
|
325
|
+
verify_callable_constraint(options_constraints)
|
326
|
+
[options_constraints]
|
205
327
|
else
|
206
|
-
|
328
|
+
scope_blocks || []
|
207
329
|
end
|
208
330
|
end
|
209
331
|
|
210
|
-
def
|
211
|
-
|
212
|
-
|
213
|
-
|
332
|
+
def constraints(options, path_params)
|
333
|
+
constraints = {}
|
334
|
+
required_defaults = []
|
335
|
+
options.each_pair do |key, option|
|
336
|
+
if Regexp === option
|
337
|
+
constraints[key] = option
|
338
|
+
else
|
339
|
+
required_defaults << key unless path_params.include?(key)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
@conditions[:required_defaults] = required_defaults
|
343
|
+
constraints
|
214
344
|
end
|
215
345
|
|
216
|
-
def
|
217
|
-
|
346
|
+
def path_params(ast)
|
347
|
+
ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
|
218
348
|
end
|
219
349
|
|
220
|
-
def
|
221
|
-
|
350
|
+
def path_ast(path)
|
351
|
+
parser = Journey::Parser.new
|
352
|
+
parser.parse path
|
222
353
|
end
|
223
354
|
|
224
|
-
def
|
225
|
-
@
|
355
|
+
def dispatcher(defaults)
|
356
|
+
@set.dispatcher defaults
|
226
357
|
end
|
227
358
|
end
|
228
359
|
|
229
|
-
# Invokes
|
360
|
+
# Invokes Journey::Router::Utils.normalize_path and ensure that
|
230
361
|
# (:locale) becomes (/:locale) instead of /(:locale). Except
|
231
362
|
# for root cases, where the latter is the correct one.
|
232
363
|
def self.normalize_path(path)
|
@@ -236,54 +367,83 @@ module ActionDispatch
|
|
236
367
|
end
|
237
368
|
|
238
369
|
def self.normalize_name(name)
|
239
|
-
normalize_path(name)[1..-1].
|
370
|
+
normalize_path(name)[1..-1].tr("/", "_")
|
240
371
|
end
|
241
372
|
|
242
373
|
module Base
|
243
374
|
# You can specify what Rails should route "/" to with the root method:
|
244
375
|
#
|
245
|
-
# root :
|
376
|
+
# root to: 'pages#main'
|
246
377
|
#
|
247
378
|
# For options, see +match+, as +root+ uses it internally.
|
248
379
|
#
|
380
|
+
# You can also pass a string which will expand
|
381
|
+
#
|
382
|
+
# root 'pages#main'
|
383
|
+
#
|
249
384
|
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
250
385
|
# because this means it will be matched first. As this is the most popular route
|
251
386
|
# of most Rails applications, this is beneficial.
|
252
387
|
def root(options = {})
|
253
|
-
match '/', { :as => :root }.merge(options)
|
388
|
+
match '/', { :as => :root, :via => :get }.merge!(options)
|
254
389
|
end
|
255
390
|
|
256
|
-
# Matches a url pattern to one or more routes.
|
257
|
-
#
|
258
|
-
# in
|
391
|
+
# Matches a url pattern to one or more routes.
|
392
|
+
#
|
393
|
+
# You should not use the +match+ method in your router
|
394
|
+
# without specifying an HTTP method.
|
395
|
+
#
|
396
|
+
# If you want to expose your action to both GET and POST, use:
|
259
397
|
#
|
260
398
|
# # sets :controller, :action and :id in params
|
261
|
-
# match ':controller/:action/:id'
|
399
|
+
# match ':controller/:action/:id', via: [:get, :post]
|
400
|
+
#
|
401
|
+
# Note that +:controller+, +:action+ and +:id+ are interpreted as url
|
402
|
+
# query parameters and thus available through +params+ in an action.
|
403
|
+
#
|
404
|
+
# If you want to expose your action to GET, use +get+ in the router:
|
405
|
+
#
|
406
|
+
# Instead of:
|
407
|
+
#
|
408
|
+
# match ":controller/:action/:id"
|
409
|
+
#
|
410
|
+
# Do:
|
411
|
+
#
|
412
|
+
# get ":controller/:action/:id"
|
262
413
|
#
|
263
414
|
# Two of these symbols are special, +:controller+ maps to the controller
|
264
415
|
# and +:action+ to the controller's action. A pattern can also map
|
265
416
|
# wildcard segments (globs) to params:
|
266
417
|
#
|
267
|
-
#
|
418
|
+
# get 'songs/*category/:title', to: 'songs#show'
|
268
419
|
#
|
269
420
|
# # 'songs/rock/classic/stairway-to-heaven' sets
|
270
421
|
# # params[:category] = 'rock/classic'
|
271
422
|
# # params[:title] = 'stairway-to-heaven'
|
272
423
|
#
|
424
|
+
# To match a wildcard parameter, it must have a name assigned to it.
|
425
|
+
# Without a variable name to attach the glob parameter to, the route
|
426
|
+
# can't be parsed.
|
427
|
+
#
|
273
428
|
# When a pattern points to an internal route, the route's +:action+ and
|
274
429
|
# +:controller+ should be set in options or hash shorthand. Examples:
|
275
430
|
#
|
276
|
-
# match 'photos/:id' => 'photos#show'
|
277
|
-
# match 'photos/:id', :
|
278
|
-
# match 'photos/:id', :
|
431
|
+
# match 'photos/:id' => 'photos#show', via: :get
|
432
|
+
# match 'photos/:id', to: 'photos#show', via: :get
|
433
|
+
# match 'photos/:id', controller: 'photos', action: 'show', via: :get
|
279
434
|
#
|
280
435
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
281
436
|
# responds to +call+:
|
282
437
|
#
|
283
|
-
# match 'photos/:id'
|
284
|
-
# match 'photos/:id'
|
438
|
+
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
|
439
|
+
# match 'photos/:id', to: PhotoRackApp, via: :get
|
285
440
|
# # Yes, controller actions are just rack endpoints
|
286
|
-
# match 'photos/:id'
|
441
|
+
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
442
|
+
#
|
443
|
+
# Because requesting various HTTP verbs with a single action has security
|
444
|
+
# implications, you must either specify the actions in
|
445
|
+
# the via options or use one of the HttpHelpers[rdoc-ref:HttpHelpers]
|
446
|
+
# instead +match+
|
287
447
|
#
|
288
448
|
# === Options
|
289
449
|
#
|
@@ -295,14 +455,20 @@ module ActionDispatch
|
|
295
455
|
# [:action]
|
296
456
|
# The route's action.
|
297
457
|
#
|
458
|
+
# [:param]
|
459
|
+
# Overrides the default resource identifier +:id+ (name of the
|
460
|
+
# dynamic segment used to generate the routes).
|
461
|
+
# You can access that segment from your controller using
|
462
|
+
# <tt>params[<:param>]</tt>.
|
463
|
+
#
|
298
464
|
# [:path]
|
299
465
|
# The path prefix for the routes.
|
300
466
|
#
|
301
467
|
# [:module]
|
302
468
|
# The namespace for :controller.
|
303
469
|
#
|
304
|
-
# match 'path'
|
305
|
-
#
|
470
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts', via: :get
|
471
|
+
# # => Sekret::PostsController
|
306
472
|
#
|
307
473
|
# See <tt>Scoping#namespace</tt> for its scope equivalent.
|
308
474
|
#
|
@@ -312,16 +478,17 @@ module ActionDispatch
|
|
312
478
|
# [:via]
|
313
479
|
# Allowed HTTP verb(s) for route.
|
314
480
|
#
|
315
|
-
# match 'path'
|
316
|
-
# match 'path'
|
481
|
+
# match 'path', to: 'c#a', via: :get
|
482
|
+
# match 'path', to: 'c#a', via: [:get, :post]
|
483
|
+
# match 'path', to: 'c#a', via: :all
|
317
484
|
#
|
318
485
|
# [:to]
|
319
486
|
# Points to a +Rack+ endpoint. Can be an object that responds to
|
320
487
|
# +call+ or a string representing a controller's action.
|
321
488
|
#
|
322
|
-
# match 'path', :
|
323
|
-
# match 'path', :
|
324
|
-
# match 'path', :
|
489
|
+
# match 'path', to: 'controller#action', via: :get
|
490
|
+
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
|
491
|
+
# match 'path', to: RackApp, via: :get
|
325
492
|
#
|
326
493
|
# [:on]
|
327
494
|
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
@@ -329,27 +496,31 @@ module ActionDispatch
|
|
329
496
|
# <tt>resource(s)</tt> block. For example:
|
330
497
|
#
|
331
498
|
# resource :bar do
|
332
|
-
# match 'foo'
|
499
|
+
# match 'foo', to: 'c#a', on: :member, via: [:get, :post]
|
333
500
|
# end
|
334
501
|
#
|
335
502
|
# Is equivalent to:
|
336
503
|
#
|
337
504
|
# resource :bar do
|
338
505
|
# member do
|
339
|
-
# match 'foo'
|
506
|
+
# match 'foo', to: 'c#a', via: [:get, :post]
|
340
507
|
# end
|
341
508
|
# end
|
342
509
|
#
|
343
510
|
# [:constraints]
|
344
|
-
# Constrains parameters with a hash of regular expressions
|
345
|
-
# object that responds to <tt>matches?</tt
|
511
|
+
# Constrains parameters with a hash of regular expressions
|
512
|
+
# or an object that responds to <tt>matches?</tt>. In addition, constraints
|
513
|
+
# other than path can also be specified with any object
|
514
|
+
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
|
346
515
|
#
|
347
|
-
# match 'path/:id', :
|
516
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
|
348
517
|
#
|
349
|
-
#
|
518
|
+
# match 'json_only', constraints: { format: 'json' }, via: :get
|
519
|
+
#
|
520
|
+
# class Whitelist
|
350
521
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
351
522
|
# end
|
352
|
-
# match 'path'
|
523
|
+
# match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
|
353
524
|
#
|
354
525
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
355
526
|
# equivalent.
|
@@ -358,7 +529,7 @@ module ActionDispatch
|
|
358
529
|
# Sets defaults for parameters
|
359
530
|
#
|
360
531
|
# # Sets params[:format] to 'jpg' by default
|
361
|
-
# match 'path'
|
532
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }, via: :get
|
362
533
|
#
|
363
534
|
# See <tt>Scoping#defaults</tt> for its scope equivalent.
|
364
535
|
#
|
@@ -367,13 +538,17 @@ module ActionDispatch
|
|
367
538
|
# false, the pattern matches any request prefixed with the given path.
|
368
539
|
#
|
369
540
|
# # Matches any request starting with 'path'
|
370
|
-
# match 'path'
|
541
|
+
# match 'path', to: 'c#a', anchor: false, via: :get
|
542
|
+
#
|
543
|
+
# [:format]
|
544
|
+
# Allows you to specify the default value for optional +format+
|
545
|
+
# segment or disable it by supplying +false+.
|
371
546
|
def match(path, options=nil)
|
372
547
|
end
|
373
548
|
|
374
549
|
# Mount a Rack-based application to be used within the application.
|
375
550
|
#
|
376
|
-
# mount SomeRackApp, :
|
551
|
+
# mount SomeRackApp, at: "some_route"
|
377
552
|
#
|
378
553
|
# Alternatively:
|
379
554
|
#
|
@@ -386,7 +561,7 @@ module ActionDispatch
|
|
386
561
|
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
|
387
562
|
# To customize this helper's name, use the +:as+ option:
|
388
563
|
#
|
389
|
-
# mount(SomeRackApp => "some_route", :
|
564
|
+
# mount(SomeRackApp => "some_route", as: "exciting")
|
390
565
|
#
|
391
566
|
# This will generate the +exciting_path+ and +exciting_url+ helpers
|
392
567
|
# which can be used to navigate to this mounted app.
|
@@ -394,18 +569,26 @@ module ActionDispatch
|
|
394
569
|
if options
|
395
570
|
path = options.delete(:at)
|
396
571
|
else
|
572
|
+
unless Hash === app
|
573
|
+
raise ArgumentError, "must be called with mount point"
|
574
|
+
end
|
575
|
+
|
397
576
|
options = app
|
398
|
-
app, path = options.find { |k,
|
577
|
+
app, path = options.find { |k, _| k.respond_to?(:call) }
|
399
578
|
options.delete(app) if app
|
400
579
|
end
|
401
580
|
|
402
581
|
raise "A rack application must be specified" unless path
|
403
582
|
|
404
|
-
|
583
|
+
rails_app = rails_app? app
|
584
|
+
options[:as] ||= app_name(app, rails_app)
|
585
|
+
|
586
|
+
target_as = name_for_action(options[:as], path)
|
587
|
+
options[:via] ||= :all
|
405
588
|
|
406
589
|
match(path, options.merge(:to => app, :anchor => false, :format => false))
|
407
590
|
|
408
|
-
define_generate_prefix(app,
|
591
|
+
define_generate_prefix(app, target_as) if rails_app
|
409
592
|
self
|
410
593
|
end
|
411
594
|
|
@@ -420,84 +603,92 @@ module ActionDispatch
|
|
420
603
|
end
|
421
604
|
end
|
422
605
|
|
606
|
+
# Query if the following named route was already defined.
|
607
|
+
def has_named_route?(name)
|
608
|
+
@set.named_routes.routes[name.to_sym]
|
609
|
+
end
|
610
|
+
|
423
611
|
private
|
424
|
-
def
|
425
|
-
|
612
|
+
def rails_app?(app)
|
613
|
+
app.is_a?(Class) && app < Rails::Railtie
|
614
|
+
end
|
426
615
|
|
427
|
-
|
616
|
+
def app_name(app, rails_app)
|
617
|
+
if rails_app
|
428
618
|
app.railtie_name
|
429
|
-
|
430
|
-
class_name = app.
|
431
|
-
ActiveSupport::Inflector.underscore(class_name).
|
619
|
+
elsif app.is_a?(Class)
|
620
|
+
class_name = app.name
|
621
|
+
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
|
432
622
|
end
|
433
623
|
end
|
434
624
|
|
435
625
|
def define_generate_prefix(app, name)
|
436
|
-
|
437
|
-
|
438
|
-
_route = @set.named_routes.routes[name.to_sym]
|
626
|
+
_route = @set.named_routes.get name
|
439
627
|
_routes = @set
|
440
628
|
app.routes.define_mounted_helper(name)
|
441
|
-
app.routes.
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
629
|
+
app.routes.extend Module.new {
|
630
|
+
def optimize_routes_generation?; false; end
|
631
|
+
define_method :find_script_name do |options|
|
632
|
+
if options.key? :script_name
|
633
|
+
super(options)
|
634
|
+
else
|
635
|
+
prefix_options = options.slice(*_route.segment_keys)
|
636
|
+
prefix_options[:relative_url_root] = ''.freeze
|
637
|
+
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
638
|
+
_route.segment_keys.each { |k| options.delete(k) }
|
639
|
+
_routes.url_helpers.send("#{name}_path", prefix_options)
|
640
|
+
end
|
449
641
|
end
|
450
|
-
|
642
|
+
}
|
451
643
|
end
|
452
644
|
end
|
453
645
|
|
454
646
|
module HttpHelpers
|
455
647
|
# Define a route that only recognizes HTTP GET.
|
456
|
-
# For supported arguments, see
|
457
|
-
#
|
458
|
-
# Example:
|
648
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
459
649
|
#
|
460
|
-
#
|
650
|
+
# get 'bacon', to: 'food#bacon'
|
461
651
|
def get(*args, &block)
|
462
|
-
map_method(:get,
|
652
|
+
map_method(:get, args, &block)
|
463
653
|
end
|
464
654
|
|
465
655
|
# Define a route that only recognizes HTTP POST.
|
466
|
-
# For supported arguments, see
|
656
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
467
657
|
#
|
468
|
-
#
|
469
|
-
#
|
470
|
-
# post 'bacon', :to => 'food#bacon'
|
658
|
+
# post 'bacon', to: 'food#bacon'
|
471
659
|
def post(*args, &block)
|
472
|
-
map_method(:post,
|
660
|
+
map_method(:post, args, &block)
|
473
661
|
end
|
474
662
|
|
475
|
-
# Define a route that only recognizes HTTP
|
476
|
-
# For supported arguments, see
|
477
|
-
#
|
478
|
-
# Example:
|
663
|
+
# Define a route that only recognizes HTTP PATCH.
|
664
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
479
665
|
#
|
480
|
-
#
|
481
|
-
def
|
482
|
-
map_method(:
|
666
|
+
# patch 'bacon', to: 'food#bacon'
|
667
|
+
def patch(*args, &block)
|
668
|
+
map_method(:patch, args, &block)
|
483
669
|
end
|
484
670
|
|
485
671
|
# Define a route that only recognizes HTTP PUT.
|
486
|
-
# For supported arguments, see
|
672
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
487
673
|
#
|
488
|
-
#
|
674
|
+
# put 'bacon', to: 'food#bacon'
|
675
|
+
def put(*args, &block)
|
676
|
+
map_method(:put, args, &block)
|
677
|
+
end
|
678
|
+
|
679
|
+
# Define a route that only recognizes HTTP DELETE.
|
680
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
489
681
|
#
|
490
|
-
#
|
682
|
+
# delete 'broccoli', to: 'food#broccoli'
|
491
683
|
def delete(*args, &block)
|
492
|
-
map_method(:delete,
|
684
|
+
map_method(:delete, args, &block)
|
493
685
|
end
|
494
686
|
|
495
687
|
private
|
496
|
-
def map_method(method,
|
688
|
+
def map_method(method, args, &block)
|
497
689
|
options = args.extract_options!
|
498
690
|
options[:via] = method
|
499
|
-
args
|
500
|
-
match(*args, &block)
|
691
|
+
match(*args, options, &block)
|
501
692
|
self
|
502
693
|
end
|
503
694
|
end
|
@@ -515,27 +706,27 @@ module ActionDispatch
|
|
515
706
|
# This will create a number of routes for each of the posts and comments
|
516
707
|
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
|
517
708
|
#
|
518
|
-
# GET
|
519
|
-
# GET
|
520
|
-
# POST
|
521
|
-
# GET
|
522
|
-
# GET
|
523
|
-
# PUT
|
524
|
-
# DELETE
|
709
|
+
# GET /admin/posts
|
710
|
+
# GET /admin/posts/new
|
711
|
+
# POST /admin/posts
|
712
|
+
# GET /admin/posts/1
|
713
|
+
# GET /admin/posts/1/edit
|
714
|
+
# PATCH/PUT /admin/posts/1
|
715
|
+
# DELETE /admin/posts/1
|
525
716
|
#
|
526
717
|
# If you want to route /posts (without the prefix /admin) to
|
527
718
|
# <tt>Admin::PostsController</tt>, you could use
|
528
719
|
#
|
529
|
-
# scope :
|
720
|
+
# scope module: "admin" do
|
530
721
|
# resources :posts
|
531
722
|
# end
|
532
723
|
#
|
533
724
|
# or, for a single case
|
534
725
|
#
|
535
|
-
# resources :posts, :
|
726
|
+
# resources :posts, module: "admin"
|
536
727
|
#
|
537
728
|
# If you want to route /admin/posts to +PostsController+
|
538
|
-
# (without the Admin
|
729
|
+
# (without the <tt>Admin::</tt> module prefix), you could use
|
539
730
|
#
|
540
731
|
# scope "/admin" do
|
541
732
|
# resources :posts
|
@@ -543,25 +734,25 @@ module ActionDispatch
|
|
543
734
|
#
|
544
735
|
# or, for a single case
|
545
736
|
#
|
546
|
-
# resources :posts, :
|
737
|
+
# resources :posts, path: "/admin/posts"
|
547
738
|
#
|
548
739
|
# In each of these cases, the named routes remain the same as if you did
|
549
740
|
# not use scope. In the last case, the following paths map to
|
550
741
|
# +PostsController+:
|
551
742
|
#
|
552
|
-
# GET
|
553
|
-
# GET
|
554
|
-
# POST
|
555
|
-
# GET
|
556
|
-
# GET
|
557
|
-
# PUT
|
558
|
-
# DELETE
|
743
|
+
# GET /admin/posts
|
744
|
+
# GET /admin/posts/new
|
745
|
+
# POST /admin/posts
|
746
|
+
# GET /admin/posts/1
|
747
|
+
# GET /admin/posts/1/edit
|
748
|
+
# PATCH/PUT /admin/posts/1
|
749
|
+
# DELETE /admin/posts/1
|
559
750
|
module Scoping
|
560
751
|
# Scopes a set of routes to the given default options.
|
561
752
|
#
|
562
753
|
# Take the following route definition as an example:
|
563
754
|
#
|
564
|
-
# scope :
|
755
|
+
# scope path: ":account_id", as: "account" do
|
565
756
|
# resources :projects
|
566
757
|
# end
|
567
758
|
#
|
@@ -573,63 +764,67 @@ module ActionDispatch
|
|
573
764
|
#
|
574
765
|
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
|
575
766
|
#
|
576
|
-
# === Examples
|
577
|
-
#
|
578
767
|
# # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
|
579
|
-
# scope :
|
768
|
+
# scope module: "admin" do
|
580
769
|
# resources :posts
|
581
770
|
# end
|
582
771
|
#
|
583
772
|
# # prefix the posts resource's requests with '/admin'
|
584
|
-
# scope :
|
773
|
+
# scope path: "/admin" do
|
585
774
|
# resources :posts
|
586
775
|
# end
|
587
776
|
#
|
588
777
|
# # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
|
589
|
-
# scope :
|
778
|
+
# scope as: "sekret" do
|
590
779
|
# resources :posts
|
591
780
|
# end
|
592
781
|
def scope(*args)
|
593
|
-
options = args.extract_options
|
594
|
-
|
595
|
-
|
596
|
-
options[:path] = args.first if args.first.is_a?(String)
|
597
|
-
recover = {}
|
782
|
+
options = args.extract_options!.dup
|
783
|
+
scope = {}
|
598
784
|
|
785
|
+
options[:path] = args.flatten.join('/') if args.any?
|
599
786
|
options[:constraints] ||= {}
|
600
|
-
|
601
|
-
|
787
|
+
|
788
|
+
unless nested_scope?
|
789
|
+
options[:shallow_path] ||= options[:path] if options.key?(:path)
|
790
|
+
options[:shallow_prefix] ||= options[:as] if options.key?(:as)
|
602
791
|
end
|
603
792
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
793
|
+
if options[:constraints].is_a?(Hash)
|
794
|
+
defaults = options[:constraints].select do |k, v|
|
795
|
+
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
|
608
796
|
end
|
797
|
+
|
798
|
+
(options[:defaults] ||= {}).reverse_merge!(defaults)
|
799
|
+
else
|
800
|
+
block, options[:constraints] = options[:constraints], {}
|
609
801
|
end
|
610
802
|
|
611
|
-
|
612
|
-
|
803
|
+
@scope.options.each do |option|
|
804
|
+
if option == :blocks
|
805
|
+
value = block
|
806
|
+
elsif option == :options
|
807
|
+
value = options
|
808
|
+
else
|
809
|
+
value = options.delete(option)
|
810
|
+
end
|
613
811
|
|
614
|
-
|
615
|
-
|
812
|
+
if value
|
813
|
+
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
814
|
+
end
|
815
|
+
end
|
616
816
|
|
817
|
+
@scope = @scope.new scope
|
617
818
|
yield
|
618
819
|
self
|
619
820
|
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]
|
821
|
+
@scope = @scope.parent
|
626
822
|
end
|
627
823
|
|
628
824
|
# Scopes routes to a specific controller
|
629
825
|
#
|
630
|
-
# Example:
|
631
826
|
# controller "food" do
|
632
|
-
# match "bacon", :
|
827
|
+
# match "bacon", action: "bacon"
|
633
828
|
# end
|
634
829
|
def controller(controller, options={})
|
635
830
|
options[:controller] = controller
|
@@ -644,13 +839,13 @@ module ActionDispatch
|
|
644
839
|
#
|
645
840
|
# This generates the following routes:
|
646
841
|
#
|
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
|
842
|
+
# admin_posts GET /admin/posts(.:format) admin/posts#index
|
843
|
+
# admin_posts POST /admin/posts(.:format) admin/posts#create
|
844
|
+
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
|
845
|
+
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
|
846
|
+
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
|
847
|
+
# admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
|
848
|
+
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
|
654
849
|
#
|
655
850
|
# === Options
|
656
851
|
#
|
@@ -660,34 +855,39 @@ module ActionDispatch
|
|
660
855
|
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
|
661
856
|
# <tt>Resources#resources</tt>.
|
662
857
|
#
|
663
|
-
# === Examples
|
664
|
-
#
|
665
858
|
# # accessible through /sekret/posts rather than /admin/posts
|
666
|
-
# namespace :admin, :
|
859
|
+
# namespace :admin, path: "sekret" do
|
667
860
|
# resources :posts
|
668
861
|
# end
|
669
862
|
#
|
670
863
|
# # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
|
671
|
-
# namespace :admin, :
|
864
|
+
# namespace :admin, module: "sekret" do
|
672
865
|
# resources :posts
|
673
866
|
# end
|
674
867
|
#
|
675
868
|
# # generates +sekret_posts_path+ rather than +admin_posts_path+
|
676
|
-
# namespace :admin, :
|
869
|
+
# namespace :admin, as: "sekret" do
|
677
870
|
# resources :posts
|
678
871
|
# end
|
679
872
|
def namespace(path, options = {})
|
680
873
|
path = path.to_s
|
681
|
-
|
682
|
-
|
683
|
-
|
874
|
+
|
875
|
+
defaults = {
|
876
|
+
module: path,
|
877
|
+
path: options.fetch(:path, path),
|
878
|
+
as: options.fetch(:as, path),
|
879
|
+
shallow_path: options.fetch(:path, path),
|
880
|
+
shallow_prefix: options.fetch(:as, path)
|
881
|
+
}
|
882
|
+
|
883
|
+
scope(defaults.merge!(options)) { yield }
|
684
884
|
end
|
685
885
|
|
686
886
|
# === Parameter Restriction
|
687
887
|
# Allows you to constrain the nested routes based on a set of rules.
|
688
888
|
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
|
689
889
|
#
|
690
|
-
# constraints(:
|
890
|
+
# constraints(id: /\d+\.\d+/) do
|
691
891
|
# resources :posts
|
692
892
|
# end
|
693
893
|
#
|
@@ -697,7 +897,7 @@ module ActionDispatch
|
|
697
897
|
# You may use this to also restrict other parameters:
|
698
898
|
#
|
699
899
|
# resources :posts do
|
700
|
-
# constraints(:
|
900
|
+
# constraints(post_id: /\d+\.\d+/) do
|
701
901
|
# resources :comments
|
702
902
|
# end
|
703
903
|
# end
|
@@ -706,7 +906,7 @@ module ActionDispatch
|
|
706
906
|
#
|
707
907
|
# Routes can also be constrained to an IP or a certain range of IP addresses:
|
708
908
|
#
|
709
|
-
# constraints(:
|
909
|
+
# constraints(ip: /192\.168\.\d+\.\d+/) do
|
710
910
|
# resources :posts
|
711
911
|
# end
|
712
912
|
#
|
@@ -743,8 +943,8 @@ module ActionDispatch
|
|
743
943
|
end
|
744
944
|
|
745
945
|
# Allows you to set default parameters for a route, such as this:
|
746
|
-
# defaults :
|
747
|
-
# match 'scoped_pages/(:id)', :
|
946
|
+
# defaults id: 'home' do
|
947
|
+
# match 'scoped_pages/(:id)', to: 'pages#show'
|
748
948
|
# end
|
749
949
|
# Using this, the +:id+ parameter here will default to 'home'.
|
750
950
|
def defaults(defaults = {})
|
@@ -752,10 +952,6 @@ module ActionDispatch
|
|
752
952
|
end
|
753
953
|
|
754
954
|
private
|
755
|
-
def scope_options #:nodoc:
|
756
|
-
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
|
757
|
-
end
|
758
|
-
|
759
955
|
def merge_path_scope(parent, child) #:nodoc:
|
760
956
|
Mapper.normalize_path("#{parent}/#{child}")
|
761
957
|
end
|
@@ -803,7 +999,7 @@ module ActionDispatch
|
|
803
999
|
end
|
804
1000
|
|
805
1001
|
def merge_options_scope(parent, child) #:nodoc:
|
806
|
-
(parent || {}).except(*override_keys(child)).merge(child)
|
1002
|
+
(parent || {}).except(*override_keys(child)).merge!(child)
|
807
1003
|
end
|
808
1004
|
|
809
1005
|
def merge_shallow_scope(parent, child) #:nodoc:
|
@@ -850,7 +1046,7 @@ module ActionDispatch
|
|
850
1046
|
# use dots as part of the +:id+ parameter add a constraint which
|
851
1047
|
# overrides this restriction, e.g:
|
852
1048
|
#
|
853
|
-
# resources :articles, :
|
1049
|
+
# resources :articles, id: /[^\/]+/
|
854
1050
|
#
|
855
1051
|
# This allows any character other than a slash as part of your +:id+.
|
856
1052
|
#
|
@@ -858,18 +1054,20 @@ module ActionDispatch
|
|
858
1054
|
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
|
859
1055
|
# a path appended since they fit properly in their scope level.
|
860
1056
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
861
|
-
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
|
1057
|
+
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
862
1058
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
863
1059
|
|
864
1060
|
class Resource #:nodoc:
|
865
|
-
attr_reader :controller, :path, :options
|
1061
|
+
attr_reader :controller, :path, :options, :param
|
866
1062
|
|
867
1063
|
def initialize(entities, options = {})
|
868
1064
|
@name = entities.to_s
|
869
1065
|
@path = (options[:path] || @name).to_s
|
870
1066
|
@controller = (options[:controller] || @name).to_s
|
871
1067
|
@as = options[:as]
|
1068
|
+
@param = (options[:param] || :id).to_sym
|
872
1069
|
@options = options
|
1070
|
+
@shallow = false
|
873
1071
|
end
|
874
1072
|
|
875
1073
|
def default_actions
|
@@ -913,17 +1111,30 @@ module ActionDispatch
|
|
913
1111
|
alias :collection_scope :path
|
914
1112
|
|
915
1113
|
def member_scope
|
916
|
-
"#{path}
|
1114
|
+
"#{path}/:#{param}"
|
917
1115
|
end
|
918
1116
|
|
1117
|
+
alias :shallow_scope :member_scope
|
1118
|
+
|
919
1119
|
def new_scope(new_path)
|
920
1120
|
"#{path}/#{new_path}"
|
921
1121
|
end
|
922
1122
|
|
1123
|
+
def nested_param
|
1124
|
+
:"#{singular}_#{param}"
|
1125
|
+
end
|
1126
|
+
|
923
1127
|
def nested_scope
|
924
|
-
"#{path}/:#{
|
1128
|
+
"#{path}/:#{nested_param}"
|
1129
|
+
end
|
1130
|
+
|
1131
|
+
def shallow=(value)
|
1132
|
+
@shallow = value
|
925
1133
|
end
|
926
1134
|
|
1135
|
+
def shallow?
|
1136
|
+
@shallow
|
1137
|
+
end
|
927
1138
|
end
|
928
1139
|
|
929
1140
|
class SingletonResource < Resource #:nodoc:
|
@@ -963,18 +1174,18 @@ module ActionDispatch
|
|
963
1174
|
# a singular resource to map /profile (rather than /profile/:id) to
|
964
1175
|
# the show action:
|
965
1176
|
#
|
966
|
-
# resource :
|
1177
|
+
# resource :profile
|
967
1178
|
#
|
968
1179
|
# creates six different routes in your application, all mapping to
|
969
|
-
# the +
|
1180
|
+
# the +Profiles+ controller (note that the controller is named after
|
970
1181
|
# the plural):
|
971
1182
|
#
|
972
|
-
# GET
|
973
|
-
# POST
|
974
|
-
# GET
|
975
|
-
# GET
|
976
|
-
# PUT
|
977
|
-
# DELETE
|
1183
|
+
# GET /profile/new
|
1184
|
+
# POST /profile
|
1185
|
+
# GET /profile
|
1186
|
+
# GET /profile/edit
|
1187
|
+
# PATCH/PUT /profile
|
1188
|
+
# DELETE /profile
|
978
1189
|
#
|
979
1190
|
# === Options
|
980
1191
|
# Takes same options as +resources+.
|
@@ -988,6 +1199,8 @@ module ActionDispatch
|
|
988
1199
|
resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
|
989
1200
|
yield if block_given?
|
990
1201
|
|
1202
|
+
concerns(options[:concerns]) if options[:concerns]
|
1203
|
+
|
991
1204
|
collection do
|
992
1205
|
post :create
|
993
1206
|
end if parent_resource.actions.include?(:create)
|
@@ -996,12 +1209,7 @@ module ActionDispatch
|
|
996
1209
|
get :new
|
997
1210
|
end if parent_resource.actions.include?(:new)
|
998
1211
|
|
999
|
-
|
1000
|
-
get :edit if parent_resource.actions.include?(:edit)
|
1001
|
-
get :show if parent_resource.actions.include?(:show)
|
1002
|
-
put :update if parent_resource.actions.include?(:update)
|
1003
|
-
delete :destroy if parent_resource.actions.include?(:destroy)
|
1004
|
-
end
|
1212
|
+
set_member_mappings_for_resource
|
1005
1213
|
end
|
1006
1214
|
|
1007
1215
|
self
|
@@ -1017,13 +1225,13 @@ module ActionDispatch
|
|
1017
1225
|
# creates seven different routes in your application, all mapping to
|
1018
1226
|
# the +Photos+ controller:
|
1019
1227
|
#
|
1020
|
-
# GET
|
1021
|
-
# GET
|
1022
|
-
# POST
|
1023
|
-
# GET
|
1024
|
-
# GET
|
1025
|
-
# PUT
|
1026
|
-
# DELETE
|
1228
|
+
# GET /photos
|
1229
|
+
# GET /photos/new
|
1230
|
+
# POST /photos
|
1231
|
+
# GET /photos/:id
|
1232
|
+
# GET /photos/:id/edit
|
1233
|
+
# PATCH/PUT /photos/:id
|
1234
|
+
# DELETE /photos/:id
|
1027
1235
|
#
|
1028
1236
|
# Resources can also be nested infinitely by using this block syntax:
|
1029
1237
|
#
|
@@ -1033,13 +1241,13 @@ module ActionDispatch
|
|
1033
1241
|
#
|
1034
1242
|
# This generates the following comments routes:
|
1035
1243
|
#
|
1036
|
-
# GET
|
1037
|
-
# GET
|
1038
|
-
# POST
|
1039
|
-
# GET
|
1040
|
-
# GET
|
1041
|
-
# PUT
|
1042
|
-
# DELETE
|
1244
|
+
# GET /photos/:photo_id/comments
|
1245
|
+
# GET /photos/:photo_id/comments/new
|
1246
|
+
# POST /photos/:photo_id/comments
|
1247
|
+
# GET /photos/:photo_id/comments/:id
|
1248
|
+
# GET /photos/:photo_id/comments/:id/edit
|
1249
|
+
# PATCH/PUT /photos/:photo_id/comments/:id
|
1250
|
+
# DELETE /photos/:photo_id/comments/:id
|
1043
1251
|
#
|
1044
1252
|
# === Options
|
1045
1253
|
# Takes same options as <tt>Base#match</tt> as well as:
|
@@ -1048,43 +1256,43 @@ module ActionDispatch
|
|
1048
1256
|
# Allows you to change the segment component of the +edit+ and +new+ actions.
|
1049
1257
|
# Actions not specified are not changed.
|
1050
1258
|
#
|
1051
|
-
# resources :posts, :
|
1259
|
+
# resources :posts, path_names: { new: "brand_new" }
|
1052
1260
|
#
|
1053
1261
|
# The above example will now change /posts/new to /posts/brand_new
|
1054
1262
|
#
|
1055
1263
|
# [:path]
|
1056
1264
|
# Allows you to change the path prefix for the resource.
|
1057
1265
|
#
|
1058
|
-
# resources :posts, :
|
1266
|
+
# resources :posts, path: 'postings'
|
1059
1267
|
#
|
1060
1268
|
# The resource and all segments will now route to /postings instead of /posts
|
1061
1269
|
#
|
1062
1270
|
# [:only]
|
1063
1271
|
# Only generate routes for the given actions.
|
1064
1272
|
#
|
1065
|
-
# resources :cows, :
|
1066
|
-
# resources :cows, :
|
1273
|
+
# resources :cows, only: :show
|
1274
|
+
# resources :cows, only: [:show, :index]
|
1067
1275
|
#
|
1068
1276
|
# [:except]
|
1069
1277
|
# Generate all routes except for the given actions.
|
1070
1278
|
#
|
1071
|
-
# resources :cows, :
|
1072
|
-
# resources :cows, :
|
1279
|
+
# resources :cows, except: :show
|
1280
|
+
# resources :cows, except: [:show, :index]
|
1073
1281
|
#
|
1074
1282
|
# [:shallow]
|
1075
1283
|
# Generates shallow routes for nested resource(s). When placed on a parent resource,
|
1076
1284
|
# generates shallow routes for all nested resources.
|
1077
1285
|
#
|
1078
|
-
# resources :posts, :
|
1286
|
+
# resources :posts, shallow: true do
|
1079
1287
|
# resources :comments
|
1080
1288
|
# end
|
1081
1289
|
#
|
1082
1290
|
# Is the same as:
|
1083
1291
|
#
|
1084
1292
|
# resources :posts do
|
1085
|
-
# resources :comments, :
|
1293
|
+
# resources :comments, except: [:show, :edit, :update, :destroy]
|
1086
1294
|
# end
|
1087
|
-
# resources :comments, :
|
1295
|
+
# resources :comments, only: [:show, :edit, :update, :destroy]
|
1088
1296
|
#
|
1089
1297
|
# This allows URLs for resources that otherwise would be deeply nested such
|
1090
1298
|
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
|
@@ -1093,29 +1301,52 @@ module ActionDispatch
|
|
1093
1301
|
# [:shallow_path]
|
1094
1302
|
# Prefixes nested shallow routes with the specified path.
|
1095
1303
|
#
|
1096
|
-
# scope :
|
1304
|
+
# scope shallow_path: "sekret" do
|
1097
1305
|
# resources :posts do
|
1098
|
-
# resources :comments, :
|
1306
|
+
# resources :comments, shallow: true
|
1099
1307
|
# end
|
1100
1308
|
# end
|
1101
1309
|
#
|
1102
1310
|
# The +comments+ resource here will have the following routes generated for it:
|
1103
1311
|
#
|
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
|
1312
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1313
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1314
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1315
|
+
# edit_comment GET /sekret/comments/:id/edit(.:format)
|
1316
|
+
# comment GET /sekret/comments/:id(.:format)
|
1317
|
+
# comment PATCH/PUT /sekret/comments/:id(.:format)
|
1318
|
+
# comment DELETE /sekret/comments/:id(.:format)
|
1319
|
+
#
|
1320
|
+
# [:shallow_prefix]
|
1321
|
+
# Prefixes nested shallow route names with specified prefix.
|
1322
|
+
#
|
1323
|
+
# scope shallow_prefix: "sekret" do
|
1324
|
+
# resources :posts do
|
1325
|
+
# resources :comments, shallow: true
|
1326
|
+
# end
|
1327
|
+
# end
|
1328
|
+
#
|
1329
|
+
# The +comments+ resource here will have the following routes generated for it:
|
1330
|
+
#
|
1331
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
1332
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
1333
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
1334
|
+
# edit_sekret_comment GET /comments/:id/edit(.:format)
|
1335
|
+
# sekret_comment GET /comments/:id(.:format)
|
1336
|
+
# sekret_comment PATCH/PUT /comments/:id(.:format)
|
1337
|
+
# sekret_comment DELETE /comments/:id(.:format)
|
1338
|
+
#
|
1339
|
+
# [:format]
|
1340
|
+
# Allows you to specify the default value for optional +format+
|
1341
|
+
# segment or disable it by supplying +false+.
|
1111
1342
|
#
|
1112
1343
|
# === Examples
|
1113
1344
|
#
|
1114
1345
|
# # routes call <tt>Admin::PostsController</tt>
|
1115
|
-
# resources :posts, :
|
1346
|
+
# resources :posts, module: "admin"
|
1116
1347
|
#
|
1117
1348
|
# # resource actions are at /admin/posts.
|
1118
|
-
# resources :posts, :
|
1349
|
+
# resources :posts, path: "admin/posts"
|
1119
1350
|
def resources(*resources, &block)
|
1120
1351
|
options = resources.extract_options!.dup
|
1121
1352
|
|
@@ -1126,6 +1357,8 @@ module ActionDispatch
|
|
1126
1357
|
resource_scope(:resources, Resource.new(resources.pop, options)) do
|
1127
1358
|
yield if block_given?
|
1128
1359
|
|
1360
|
+
concerns(options[:concerns]) if options[:concerns]
|
1361
|
+
|
1129
1362
|
collection do
|
1130
1363
|
get :index if parent_resource.actions.include?(:index)
|
1131
1364
|
post :create if parent_resource.actions.include?(:create)
|
@@ -1135,12 +1368,7 @@ module ActionDispatch
|
|
1135
1368
|
get :new
|
1136
1369
|
end if parent_resource.actions.include?(:new)
|
1137
1370
|
|
1138
|
-
|
1139
|
-
get :edit if parent_resource.actions.include?(:edit)
|
1140
|
-
get :show if parent_resource.actions.include?(:show)
|
1141
|
-
put :update if parent_resource.actions.include?(:update)
|
1142
|
-
delete :destroy if parent_resource.actions.include?(:destroy)
|
1143
|
-
end
|
1371
|
+
set_member_mappings_for_resource
|
1144
1372
|
end
|
1145
1373
|
|
1146
1374
|
self
|
@@ -1187,8 +1415,10 @@ module ActionDispatch
|
|
1187
1415
|
end
|
1188
1416
|
|
1189
1417
|
with_scope_level(:member) do
|
1190
|
-
|
1191
|
-
yield
|
1418
|
+
if shallow?
|
1419
|
+
shallow_scope(parent_resource.member_scope) { yield }
|
1420
|
+
else
|
1421
|
+
scope(parent_resource.member_scope) { yield }
|
1192
1422
|
end
|
1193
1423
|
end
|
1194
1424
|
end
|
@@ -1211,16 +1441,8 @@ module ActionDispatch
|
|
1211
1441
|
end
|
1212
1442
|
|
1213
1443
|
with_scope_level(:nested) do
|
1214
|
-
if shallow?
|
1215
|
-
|
1216
|
-
if @scope[:shallow_path].blank?
|
1217
|
-
scope(parent_resource.nested_scope, nested_options) { yield }
|
1218
|
-
else
|
1219
|
-
scope(@scope[:shallow_path], :as => @scope[:shallow_prefix]) do
|
1220
|
-
scope(parent_resource.nested_scope, nested_options) { yield }
|
1221
|
-
end
|
1222
|
-
end
|
1223
|
-
end
|
1444
|
+
if shallow? && shallow_nesting_depth >= 1
|
1445
|
+
shallow_scope(parent_resource.nested_scope, nested_options) { yield }
|
1224
1446
|
else
|
1225
1447
|
scope(parent_resource.nested_scope, nested_options) { yield }
|
1226
1448
|
end
|
@@ -1237,7 +1459,7 @@ module ActionDispatch
|
|
1237
1459
|
end
|
1238
1460
|
|
1239
1461
|
def shallow
|
1240
|
-
scope(:shallow => true
|
1462
|
+
scope(:shallow => true) do
|
1241
1463
|
yield
|
1242
1464
|
end
|
1243
1465
|
end
|
@@ -1246,11 +1468,27 @@ module ActionDispatch
|
|
1246
1468
|
parent_resource.instance_of?(Resource) && @scope[:shallow]
|
1247
1469
|
end
|
1248
1470
|
|
1471
|
+
# match 'path' => 'controller#action'
|
1472
|
+
# match 'path', to: 'controller#action'
|
1473
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1249
1474
|
def match(path, *rest)
|
1250
1475
|
if rest.empty? && Hash === path
|
1251
1476
|
options = path
|
1252
|
-
path, to = options.find { |name,
|
1253
|
-
|
1477
|
+
path, to = options.find { |name, _value| name.is_a?(String) }
|
1478
|
+
|
1479
|
+
case to
|
1480
|
+
when Symbol
|
1481
|
+
options[:action] = to
|
1482
|
+
when String
|
1483
|
+
if to =~ /#/
|
1484
|
+
options[:to] = to
|
1485
|
+
else
|
1486
|
+
options[:controller] = to
|
1487
|
+
end
|
1488
|
+
else
|
1489
|
+
options[:to] = to
|
1490
|
+
end
|
1491
|
+
|
1254
1492
|
options.delete(path)
|
1255
1493
|
paths = [path]
|
1256
1494
|
else
|
@@ -1258,34 +1496,40 @@ module ActionDispatch
|
|
1258
1496
|
paths = [path] + rest
|
1259
1497
|
end
|
1260
1498
|
|
1499
|
+
options[:anchor] = true unless options.key?(:anchor)
|
1500
|
+
|
1501
|
+
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1502
|
+
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1503
|
+
end
|
1504
|
+
|
1261
1505
|
if @scope[:controller] && @scope[:action]
|
1262
1506
|
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1263
1507
|
end
|
1264
1508
|
|
1265
|
-
|
1266
|
-
|
1267
|
-
|
1268
|
-
end
|
1509
|
+
paths.each do |_path|
|
1510
|
+
route_options = options.dup
|
1511
|
+
route_options[:path] ||= _path if _path.is_a?(String)
|
1269
1512
|
|
1270
|
-
|
1513
|
+
path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
|
1514
|
+
if using_match_shorthand?(path_without_format, route_options)
|
1515
|
+
route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
1516
|
+
route_options[:to].tr!("-", "_")
|
1517
|
+
end
|
1271
1518
|
|
1272
|
-
|
1273
|
-
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1519
|
+
decomposed_match(_path, route_options)
|
1274
1520
|
end
|
1275
|
-
|
1276
|
-
paths.each { |_path| decomposed_match(_path, options.dup) }
|
1277
1521
|
self
|
1278
1522
|
end
|
1279
1523
|
|
1280
1524
|
def using_match_shorthand?(path, options)
|
1281
|
-
path && (options[:to] || options[:action]).nil? && path =~ %r{
|
1525
|
+
path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1282
1526
|
end
|
1283
1527
|
|
1284
1528
|
def decomposed_match(path, options) # :nodoc:
|
1285
1529
|
if on = options.delete(:on)
|
1286
1530
|
send(on) { decomposed_match(path, options) }
|
1287
1531
|
else
|
1288
|
-
case @scope
|
1532
|
+
case @scope.scope_level
|
1289
1533
|
when :resources
|
1290
1534
|
nested { decomposed_match(path, options) }
|
1291
1535
|
when :resource
|
@@ -1298,27 +1542,37 @@ module ActionDispatch
|
|
1298
1542
|
|
1299
1543
|
def add_route(action, options) # :nodoc:
|
1300
1544
|
path = path_for_action(action, options.delete(:path))
|
1545
|
+
raise ArgumentError, "path is required" if path.blank?
|
1546
|
+
|
1301
1547
|
action = action.to_s.dup
|
1302
1548
|
|
1303
|
-
if action =~ /^[\w
|
1304
|
-
options[:action] ||= action unless action.include?("/")
|
1549
|
+
if action =~ /^[\w\-\/]+$/
|
1550
|
+
options[:action] ||= action.tr('-', '_') unless action.include?("/")
|
1305
1551
|
else
|
1306
1552
|
action = nil
|
1307
1553
|
end
|
1308
1554
|
|
1309
|
-
if !options.fetch(:as, true)
|
1310
|
-
|
1311
|
-
|
1312
|
-
|
1313
|
-
|
1555
|
+
as = if !options.fetch(:as, true) # if it's set to nil or false
|
1556
|
+
options.delete(:as)
|
1557
|
+
else
|
1558
|
+
name_for_action(options.delete(:as), action)
|
1559
|
+
end
|
1314
1560
|
|
1315
|
-
mapping = Mapping.
|
1561
|
+
mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
|
1316
1562
|
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
1317
1563
|
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
1318
1564
|
end
|
1319
1565
|
|
1320
|
-
def root(options={})
|
1321
|
-
if
|
1566
|
+
def root(path, options={})
|
1567
|
+
if path.is_a?(String)
|
1568
|
+
options[:to] = path
|
1569
|
+
elsif path.is_a?(Hash) and options.empty?
|
1570
|
+
options = path
|
1571
|
+
else
|
1572
|
+
raise ArgumentError, "must be called with a path and/or options"
|
1573
|
+
end
|
1574
|
+
|
1575
|
+
if @scope.resources?
|
1322
1576
|
with_scope_level(:root) do
|
1323
1577
|
scope(parent_resource.path) do
|
1324
1578
|
super(options)
|
@@ -1341,6 +1595,13 @@ module ActionDispatch
|
|
1341
1595
|
return true
|
1342
1596
|
end
|
1343
1597
|
|
1598
|
+
if options.delete(:shallow)
|
1599
|
+
shallow do
|
1600
|
+
send(method, resources.pop, options, &block)
|
1601
|
+
end
|
1602
|
+
return true
|
1603
|
+
end
|
1604
|
+
|
1344
1605
|
if resource_scope?
|
1345
1606
|
nested { send(method, resources.pop, options, &block) }
|
1346
1607
|
return true
|
@@ -1378,96 +1639,115 @@ module ActionDispatch
|
|
1378
1639
|
end
|
1379
1640
|
|
1380
1641
|
def resource_scope? #:nodoc:
|
1381
|
-
|
1642
|
+
@scope.resource_scope?
|
1382
1643
|
end
|
1383
1644
|
|
1384
1645
|
def resource_method_scope? #:nodoc:
|
1385
|
-
|
1646
|
+
@scope.resource_method_scope?
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
def nested_scope? #:nodoc:
|
1650
|
+
@scope.nested?
|
1386
1651
|
end
|
1387
1652
|
|
1388
1653
|
def with_exclusive_scope
|
1389
1654
|
begin
|
1390
|
-
|
1391
|
-
@scope[:as], @scope[:path] = nil, nil
|
1655
|
+
@scope = @scope.new(:as => nil, :path => nil)
|
1392
1656
|
|
1393
1657
|
with_scope_level(:exclusive) do
|
1394
1658
|
yield
|
1395
1659
|
end
|
1396
1660
|
ensure
|
1397
|
-
@scope
|
1661
|
+
@scope = @scope.parent
|
1398
1662
|
end
|
1399
1663
|
end
|
1400
1664
|
|
1401
|
-
def with_scope_level(kind
|
1402
|
-
|
1403
|
-
old_resource, @scope[:scope_level_resource] = @scope[:scope_level_resource], resource
|
1665
|
+
def with_scope_level(kind)
|
1666
|
+
@scope = @scope.new_level(kind)
|
1404
1667
|
yield
|
1405
1668
|
ensure
|
1406
|
-
@scope
|
1407
|
-
@scope[:scope_level_resource] = old_resource
|
1669
|
+
@scope = @scope.parent
|
1408
1670
|
end
|
1409
1671
|
|
1410
1672
|
def resource_scope(kind, resource) #:nodoc:
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1673
|
+
resource.shallow = @scope[:shallow]
|
1674
|
+
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
+
@nesting.push(resource)
|
1676
|
+
|
1677
|
+
with_scope_level(kind) do
|
1678
|
+
scope(parent_resource.resource_scope) { yield }
|
1415
1679
|
end
|
1680
|
+
ensure
|
1681
|
+
@nesting.pop
|
1682
|
+
@scope = @scope.parent
|
1416
1683
|
end
|
1417
1684
|
|
1418
1685
|
def nested_options #:nodoc:
|
1419
1686
|
options = { :as => parent_resource.member_name }
|
1420
1687
|
options[:constraints] = {
|
1421
|
-
|
1422
|
-
} if
|
1688
|
+
parent_resource.nested_param => param_constraint
|
1689
|
+
} if param_constraint?
|
1423
1690
|
|
1424
1691
|
options
|
1425
1692
|
end
|
1426
1693
|
|
1427
|
-
def
|
1428
|
-
@
|
1694
|
+
def nesting_depth #:nodoc:
|
1695
|
+
@nesting.size
|
1429
1696
|
end
|
1430
1697
|
|
1431
|
-
def
|
1432
|
-
@
|
1698
|
+
def shallow_nesting_depth #:nodoc:
|
1699
|
+
@nesting.select(&:shallow?).size
|
1433
1700
|
end
|
1434
1701
|
|
1435
|
-
def
|
1436
|
-
|
1702
|
+
def param_constraint? #:nodoc:
|
1703
|
+
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
1437
1704
|
end
|
1438
1705
|
|
1439
|
-
def
|
1440
|
-
|
1706
|
+
def param_constraint #:nodoc:
|
1707
|
+
@scope[:constraints][parent_resource.param]
|
1441
1708
|
end
|
1442
1709
|
|
1443
|
-
def
|
1444
|
-
|
1445
|
-
|
1710
|
+
def canonical_action?(action) #:nodoc:
|
1711
|
+
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1712
|
+
end
|
1713
|
+
|
1714
|
+
def shallow_scope(path, options = {}) #:nodoc:
|
1715
|
+
scope = { :as => @scope[:shallow_prefix],
|
1716
|
+
:path => @scope[:shallow_path] }
|
1717
|
+
@scope = @scope.new scope
|
1446
1718
|
|
1447
|
-
path
|
1448
|
-
|
1719
|
+
scope(path, options) { yield }
|
1720
|
+
ensure
|
1721
|
+
@scope = @scope.parent
|
1722
|
+
end
|
1723
|
+
|
1724
|
+
def path_for_action(action, path) #:nodoc:
|
1725
|
+
if path.blank? && canonical_action?(action)
|
1726
|
+
@scope[:path].to_s
|
1449
1727
|
else
|
1450
|
-
"#{
|
1728
|
+
"#{@scope[:path]}/#{action_path(action, path)}"
|
1451
1729
|
end
|
1452
1730
|
end
|
1453
1731
|
|
1454
1732
|
def action_path(name, path = nil) #:nodoc:
|
1455
|
-
|
1456
|
-
name = name.to_sym if name.is_a?(String) && !name.empty?
|
1733
|
+
name = name.to_sym if name.is_a?(String)
|
1457
1734
|
path || @scope[:path_names][name] || name.to_s
|
1458
1735
|
end
|
1459
1736
|
|
1460
1737
|
def prefix_name_for_action(as, action) #:nodoc:
|
1461
1738
|
if as
|
1462
|
-
as
|
1463
|
-
elsif !canonical_action?(action
|
1464
|
-
action
|
1739
|
+
prefix = as
|
1740
|
+
elsif !canonical_action?(action)
|
1741
|
+
prefix = action
|
1742
|
+
end
|
1743
|
+
|
1744
|
+
if prefix && prefix != '/' && !prefix.empty?
|
1745
|
+
Mapper.normalize_name prefix.to_s.tr('-', '_')
|
1465
1746
|
end
|
1466
1747
|
end
|
1467
1748
|
|
1468
1749
|
def name_for_action(as, action) #:nodoc:
|
1469
1750
|
prefix = prefix_name_for_action(as, action)
|
1470
|
-
prefix = Mapper.normalize_name(prefix) if prefix
|
1471
1751
|
name_prefix = @scope[:as]
|
1472
1752
|
|
1473
1753
|
if parent_resource
|
@@ -1477,43 +1757,224 @@ module ActionDispatch
|
|
1477
1757
|
member_name = parent_resource.member_name
|
1478
1758
|
end
|
1479
1759
|
|
1480
|
-
name =
|
1481
|
-
when :nested
|
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
|
1760
|
+
name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1494
1761
|
|
1495
|
-
if candidate = name.
|
1762
|
+
if candidate = name.compact.join("_").presence
|
1496
1763
|
# If a name was not explicitly given, we check if it is valid
|
1497
1764
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1498
1765
|
# forward so the underlying router engine treats it and raises an exception.
|
1499
1766
|
if as.nil?
|
1500
|
-
candidate unless
|
1767
|
+
candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
|
1501
1768
|
else
|
1502
1769
|
candidate
|
1503
1770
|
end
|
1504
1771
|
end
|
1505
1772
|
end
|
1773
|
+
|
1774
|
+
def set_member_mappings_for_resource
|
1775
|
+
member do
|
1776
|
+
get :edit if parent_resource.actions.include?(:edit)
|
1777
|
+
get :show if parent_resource.actions.include?(:show)
|
1778
|
+
if parent_resource.actions.include?(:update)
|
1779
|
+
patch :update
|
1780
|
+
put :update
|
1781
|
+
end
|
1782
|
+
delete :destroy if parent_resource.actions.include?(:destroy)
|
1783
|
+
end
|
1784
|
+
end
|
1785
|
+
end
|
1786
|
+
|
1787
|
+
# Routing Concerns allow you to declare common routes that can be reused
|
1788
|
+
# inside others resources and routes.
|
1789
|
+
#
|
1790
|
+
# concern :commentable do
|
1791
|
+
# resources :comments
|
1792
|
+
# end
|
1793
|
+
#
|
1794
|
+
# concern :image_attachable do
|
1795
|
+
# resources :images, only: :index
|
1796
|
+
# end
|
1797
|
+
#
|
1798
|
+
# These concerns are used in Resources routing:
|
1799
|
+
#
|
1800
|
+
# resources :messages, concerns: [:commentable, :image_attachable]
|
1801
|
+
#
|
1802
|
+
# or in a scope or namespace:
|
1803
|
+
#
|
1804
|
+
# namespace :posts do
|
1805
|
+
# concerns :commentable
|
1806
|
+
# end
|
1807
|
+
module Concerns
|
1808
|
+
# Define a routing concern using a name.
|
1809
|
+
#
|
1810
|
+
# Concerns may be defined inline, using a block, or handled by
|
1811
|
+
# another object, by passing that object as the second parameter.
|
1812
|
+
#
|
1813
|
+
# The concern object, if supplied, should respond to <tt>call</tt>,
|
1814
|
+
# which will receive two parameters:
|
1815
|
+
#
|
1816
|
+
# * The current mapper
|
1817
|
+
# * A hash of options which the concern object may use
|
1818
|
+
#
|
1819
|
+
# Options may also be used by concerns defined in a block by accepting
|
1820
|
+
# a block parameter. So, using a block, you might do something as
|
1821
|
+
# simple as limit the actions available on certain resources, passing
|
1822
|
+
# standard resource options through the concern:
|
1823
|
+
#
|
1824
|
+
# concern :commentable do |options|
|
1825
|
+
# resources :comments, options
|
1826
|
+
# end
|
1827
|
+
#
|
1828
|
+
# resources :posts, concerns: :commentable
|
1829
|
+
# resources :archived_posts do
|
1830
|
+
# # Don't allow comments on archived posts
|
1831
|
+
# concerns :commentable, only: [:index, :show]
|
1832
|
+
# end
|
1833
|
+
#
|
1834
|
+
# Or, using a callable object, you might implement something more
|
1835
|
+
# specific to your application, which would be out of place in your
|
1836
|
+
# routes file.
|
1837
|
+
#
|
1838
|
+
# # purchasable.rb
|
1839
|
+
# class Purchasable
|
1840
|
+
# def initialize(defaults = {})
|
1841
|
+
# @defaults = defaults
|
1842
|
+
# end
|
1843
|
+
#
|
1844
|
+
# def call(mapper, options = {})
|
1845
|
+
# options = @defaults.merge(options)
|
1846
|
+
# mapper.resources :purchases
|
1847
|
+
# mapper.resources :receipts
|
1848
|
+
# mapper.resources :returns if options[:returnable]
|
1849
|
+
# end
|
1850
|
+
# end
|
1851
|
+
#
|
1852
|
+
# # routes.rb
|
1853
|
+
# concern :purchasable, Purchasable.new(returnable: true)
|
1854
|
+
#
|
1855
|
+
# resources :toys, concerns: :purchasable
|
1856
|
+
# resources :electronics, concerns: :purchasable
|
1857
|
+
# resources :pets do
|
1858
|
+
# concerns :purchasable, returnable: false
|
1859
|
+
# end
|
1860
|
+
#
|
1861
|
+
# Any routing helpers can be used inside a concern. If using a
|
1862
|
+
# callable, they're accessible from the Mapper that's passed to
|
1863
|
+
# <tt>call</tt>.
|
1864
|
+
def concern(name, callable = nil, &block)
|
1865
|
+
callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
|
1866
|
+
@concerns[name] = callable
|
1867
|
+
end
|
1868
|
+
|
1869
|
+
# Use the named concerns
|
1870
|
+
#
|
1871
|
+
# resources :posts do
|
1872
|
+
# concerns :commentable
|
1873
|
+
# end
|
1874
|
+
#
|
1875
|
+
# concerns also work in any routes helper that you want to use:
|
1876
|
+
#
|
1877
|
+
# namespace :posts do
|
1878
|
+
# concerns :commentable
|
1879
|
+
# end
|
1880
|
+
def concerns(*args)
|
1881
|
+
options = args.extract_options!
|
1882
|
+
args.flatten.each do |name|
|
1883
|
+
if concern = @concerns[name]
|
1884
|
+
concern.call(self, options)
|
1885
|
+
else
|
1886
|
+
raise ArgumentError, "No concern named #{name} was found!"
|
1887
|
+
end
|
1888
|
+
end
|
1889
|
+
end
|
1890
|
+
end
|
1891
|
+
|
1892
|
+
class Scope # :nodoc:
|
1893
|
+
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
|
+
:controller, :action, :path_names, :constraints,
|
1895
|
+
:shallow, :blocks, :defaults, :options]
|
1896
|
+
|
1897
|
+
RESOURCE_SCOPES = [:resource, :resources]
|
1898
|
+
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
|
+
|
1900
|
+
attr_reader :parent, :scope_level
|
1901
|
+
|
1902
|
+
def initialize(hash, parent = {}, scope_level = nil)
|
1903
|
+
@hash = hash
|
1904
|
+
@parent = parent
|
1905
|
+
@scope_level = scope_level
|
1906
|
+
end
|
1907
|
+
|
1908
|
+
def nested?
|
1909
|
+
scope_level == :nested
|
1910
|
+
end
|
1911
|
+
|
1912
|
+
def resources?
|
1913
|
+
scope_level == :resources
|
1914
|
+
end
|
1915
|
+
|
1916
|
+
def resource_method_scope?
|
1917
|
+
RESOURCE_METHOD_SCOPES.include? scope_level
|
1918
|
+
end
|
1919
|
+
|
1920
|
+
def action_name(name_prefix, prefix, collection_name, member_name)
|
1921
|
+
case scope_level
|
1922
|
+
when :nested
|
1923
|
+
[name_prefix, prefix]
|
1924
|
+
when :collection
|
1925
|
+
[prefix, name_prefix, collection_name]
|
1926
|
+
when :new
|
1927
|
+
[prefix, :new, name_prefix, member_name]
|
1928
|
+
when :member
|
1929
|
+
[prefix, name_prefix, member_name]
|
1930
|
+
when :root
|
1931
|
+
[name_prefix, collection_name, prefix]
|
1932
|
+
else
|
1933
|
+
[name_prefix, member_name, prefix]
|
1934
|
+
end
|
1935
|
+
end
|
1936
|
+
|
1937
|
+
def resource_scope?
|
1938
|
+
RESOURCE_SCOPES.include? scope_level
|
1939
|
+
end
|
1940
|
+
|
1941
|
+
def options
|
1942
|
+
OPTIONS
|
1943
|
+
end
|
1944
|
+
|
1945
|
+
def new(hash)
|
1946
|
+
self.class.new hash, self, scope_level
|
1947
|
+
end
|
1948
|
+
|
1949
|
+
def new_level(level)
|
1950
|
+
self.class.new(self, self, level)
|
1951
|
+
end
|
1952
|
+
|
1953
|
+
def fetch(key, &block)
|
1954
|
+
@hash.fetch(key, &block)
|
1955
|
+
end
|
1956
|
+
|
1957
|
+
def [](key)
|
1958
|
+
@hash.fetch(key) { @parent[key] }
|
1959
|
+
end
|
1960
|
+
|
1961
|
+
def []=(k,v)
|
1962
|
+
@hash[k] = v
|
1963
|
+
end
|
1506
1964
|
end
|
1507
1965
|
|
1508
1966
|
def initialize(set) #:nodoc:
|
1509
1967
|
@set = set
|
1510
|
-
@scope = { :path_names => @set.resources_path_names }
|
1968
|
+
@scope = Scope.new({ :path_names => @set.resources_path_names })
|
1969
|
+
@concerns = {}
|
1970
|
+
@nesting = []
|
1511
1971
|
end
|
1512
1972
|
|
1513
1973
|
include Base
|
1514
1974
|
include HttpHelpers
|
1515
1975
|
include Redirection
|
1516
1976
|
include Scoping
|
1977
|
+
include Concerns
|
1517
1978
|
include Resources
|
1518
1979
|
end
|
1519
1980
|
end
|