actionpack 3.2.19 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +850 -401
- data/MIT-LICENSE +1 -1
- data/README.rdoc +5 -288
- data/lib/abstract_controller/asset_paths.rb +2 -2
- data/lib/abstract_controller/base.rb +39 -37
- data/lib/abstract_controller/callbacks.rb +101 -82
- data/lib/abstract_controller/collector.rb +7 -3
- data/lib/abstract_controller/helpers.rb +25 -13
- data/lib/abstract_controller/layouts.rb +74 -74
- data/lib/abstract_controller/logger.rb +1 -2
- data/lib/abstract_controller/rendering.rb +30 -13
- data/lib/abstract_controller/translation.rb +16 -1
- data/lib/abstract_controller/url_for.rb +6 -6
- data/lib/abstract_controller/view_paths.rb +1 -1
- data/lib/abstract_controller.rb +1 -8
- data/lib/action_controller/base.rb +46 -22
- data/lib/action_controller/caching/fragments.rb +23 -53
- data/lib/action_controller/caching.rb +46 -33
- data/lib/action_controller/deprecated/integration_test.rb +3 -0
- data/lib/action_controller/deprecated.rb +5 -1
- data/lib/action_controller/log_subscriber.rb +16 -8
- data/lib/action_controller/metal/conditional_get.rb +76 -32
- data/lib/action_controller/metal/data_streaming.rb +20 -26
- data/lib/action_controller/metal/exceptions.rb +19 -6
- data/lib/action_controller/metal/flash.rb +24 -9
- data/lib/action_controller/metal/force_ssl.rb +70 -12
- data/lib/action_controller/metal/head.rb +25 -4
- data/lib/action_controller/metal/helpers.rb +5 -9
- data/lib/action_controller/metal/hide_actions.rb +0 -1
- data/lib/action_controller/metal/http_authentication.rb +107 -83
- data/lib/action_controller/metal/implicit_render.rb +1 -1
- data/lib/action_controller/metal/instrumentation.rb +2 -1
- data/lib/action_controller/metal/live.rb +175 -0
- data/lib/action_controller/metal/mime_responds.rb +161 -47
- data/lib/action_controller/metal/params_wrapper.rb +112 -74
- data/lib/action_controller/metal/rack_delegation.rb +9 -3
- data/lib/action_controller/metal/redirecting.rb +15 -20
- data/lib/action_controller/metal/renderers.rb +11 -9
- data/lib/action_controller/metal/rendering.rb +9 -1
- data/lib/action_controller/metal/request_forgery_protection.rb +112 -19
- data/lib/action_controller/metal/responder.rb +20 -19
- data/lib/action_controller/metal/streaming.rb +12 -18
- data/lib/action_controller/metal/strong_parameters.rb +520 -0
- data/lib/action_controller/metal/testing.rb +13 -18
- data/lib/action_controller/metal/url_for.rb +28 -25
- data/lib/action_controller/metal.rb +17 -32
- data/lib/action_controller/model_naming.rb +12 -0
- data/lib/action_controller/railtie.rb +33 -17
- data/lib/action_controller/railties/helpers.rb +22 -0
- data/lib/action_controller/record_identifier.rb +18 -72
- data/lib/action_controller/test_case.rb +251 -131
- data/lib/action_controller/vendor/html-scanner.rb +4 -19
- data/lib/action_controller.rb +15 -6
- data/lib/action_dispatch/http/cache.rb +63 -11
- data/lib/action_dispatch/http/filter_parameters.rb +18 -8
- data/lib/action_dispatch/http/filter_redirect.rb +37 -0
- data/lib/action_dispatch/http/headers.rb +49 -17
- data/lib/action_dispatch/http/mime_negotiation.rb +24 -1
- data/lib/action_dispatch/http/mime_type.rb +154 -100
- data/lib/action_dispatch/http/mime_types.rb +1 -1
- data/lib/action_dispatch/http/parameter_filter.rb +44 -46
- data/lib/action_dispatch/http/parameters.rb +28 -28
- data/lib/action_dispatch/http/rack_cache.rb +2 -3
- data/lib/action_dispatch/http/request.rb +64 -18
- data/lib/action_dispatch/http/response.rb +130 -35
- data/lib/action_dispatch/http/upload.rb +63 -20
- data/lib/action_dispatch/http/url.rb +98 -35
- data/lib/action_dispatch/journey/backwards.rb +5 -0
- data/lib/action_dispatch/journey/formatter.rb +146 -0
- data/lib/action_dispatch/journey/gtg/builder.rb +162 -0
- data/lib/action_dispatch/journey/gtg/simulator.rb +44 -0
- data/lib/action_dispatch/journey/gtg/transition_table.rb +156 -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 +124 -0
- data/lib/action_dispatch/journey/parser.rb +206 -0
- data/lib/action_dispatch/journey/parser.y +47 -0
- data/lib/action_dispatch/journey/parser_extras.rb +23 -0
- data/lib/action_dispatch/journey/path/pattern.rb +196 -0
- data/lib/action_dispatch/journey/route.rb +124 -0
- data/lib/action_dispatch/journey/router/strexp.rb +24 -0
- data/lib/action_dispatch/journey/router/utils.rb +54 -0
- data/lib/action_dispatch/journey/router.rb +166 -0
- data/lib/action_dispatch/journey/routes.rb +75 -0
- data/lib/action_dispatch/journey/scanner.rb +61 -0
- data/lib/action_dispatch/journey/visitors.rb +197 -0
- data/lib/action_dispatch/journey/visualizer/fsm.css +34 -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 +9 -4
- data/lib/action_dispatch/middleware/cookies.rb +259 -114
- data/lib/action_dispatch/middleware/debug_exceptions.rb +26 -17
- data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -3
- data/lib/action_dispatch/middleware/flash.rb +58 -58
- data/lib/action_dispatch/middleware/params_parser.rb +14 -29
- data/lib/action_dispatch/middleware/public_exceptions.rb +30 -14
- data/lib/action_dispatch/middleware/reloader.rb +6 -6
- data/lib/action_dispatch/middleware/remote_ip.rb +145 -39
- data/lib/action_dispatch/middleware/request_id.rb +2 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +22 -20
- data/lib/action_dispatch/middleware/session/cookie_store.rb +82 -28
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +8 -3
- data/lib/action_dispatch/middleware/show_exceptions.rb +12 -45
- data/lib/action_dispatch/middleware/ssl.rb +70 -0
- data/lib/action_dispatch/middleware/stack.rb +6 -1
- data/lib/action_dispatch/middleware/static.rb +2 -1
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb +14 -11
- data/lib/action_dispatch/middleware/templates/rescues/_source.erb +25 -0
- data/lib/action_dispatch/middleware/templates/rescues/_trace.erb +7 -9
- data/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb +15 -9
- data/lib/action_dispatch/middleware/templates/rescues/layout.erb +127 -5
- data/lib/action_dispatch/middleware/templates/rescues/missing_template.erb +7 -2
- data/lib/action_dispatch/middleware/templates/rescues/routing_error.erb +30 -15
- data/lib/action_dispatch/middleware/templates/rescues/template_error.erb +39 -13
- data/lib/action_dispatch/middleware/templates/rescues/unknown_action.erb +6 -2
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +16 -0
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +144 -0
- data/lib/action_dispatch/railtie.rb +16 -6
- data/lib/action_dispatch/request/session.rb +181 -0
- data/lib/action_dispatch/routing/inspector.rb +240 -0
- data/lib/action_dispatch/routing/mapper.rb +540 -291
- data/lib/action_dispatch/routing/polymorphic_routes.rb +16 -20
- data/lib/action_dispatch/routing/redirection.rb +46 -29
- data/lib/action_dispatch/routing/route_set.rb +207 -164
- data/lib/action_dispatch/routing/routes_proxy.rb +2 -0
- data/lib/action_dispatch/routing/url_for.rb +48 -33
- data/lib/action_dispatch/routing.rb +48 -83
- data/lib/action_dispatch/testing/assertions/dom.rb +3 -13
- data/lib/action_dispatch/testing/assertions/response.rb +32 -40
- data/lib/action_dispatch/testing/assertions/routing.rb +42 -41
- data/lib/action_dispatch/testing/assertions/selector.rb +17 -22
- data/lib/action_dispatch/testing/assertions/tag.rb +20 -23
- data/lib/action_dispatch/testing/integration.rb +65 -51
- data/lib/action_dispatch/testing/test_process.rb +9 -6
- data/lib/action_dispatch/testing/test_request.rb +7 -3
- data/lib/action_dispatch.rb +21 -15
- data/lib/action_pack/version.rb +7 -6
- data/lib/action_pack.rb +1 -1
- data/lib/action_view/base.rb +15 -34
- data/lib/action_view/buffers.rb +7 -1
- data/lib/action_view/context.rb +4 -4
- data/lib/action_view/dependency_tracker.rb +93 -0
- data/lib/action_view/digestor.rb +85 -0
- data/lib/action_view/flows.rb +1 -4
- data/lib/action_view/helpers/active_model_helper.rb +3 -4
- data/lib/action_view/helpers/asset_tag_helper.rb +215 -352
- data/lib/action_view/helpers/asset_url_helper.rb +355 -0
- data/lib/action_view/helpers/atom_feed_helper.rb +13 -10
- data/lib/action_view/helpers/cache_helper.rb +150 -18
- data/lib/action_view/helpers/capture_helper.rb +44 -31
- data/lib/action_view/helpers/csrf_helper.rb +0 -2
- data/lib/action_view/helpers/date_helper.rb +269 -248
- data/lib/action_view/helpers/debug_helper.rb +10 -11
- data/lib/action_view/helpers/form_helper.rb +931 -537
- data/lib/action_view/helpers/form_options_helper.rb +341 -166
- data/lib/action_view/helpers/form_tag_helper.rb +190 -90
- data/lib/action_view/helpers/javascript_helper.rb +23 -16
- data/lib/action_view/helpers/number_helper.rb +148 -329
- data/lib/action_view/helpers/output_safety_helper.rb +3 -3
- data/lib/action_view/helpers/record_tag_helper.rb +17 -22
- data/lib/action_view/helpers/rendering_helper.rb +2 -2
- data/lib/action_view/helpers/sanitize_helper.rb +3 -6
- data/lib/action_view/helpers/tag_helper.rb +46 -33
- data/lib/action_view/helpers/tags/base.rb +147 -0
- data/lib/action_view/helpers/tags/check_box.rb +64 -0
- data/lib/action_view/helpers/tags/checkable.rb +16 -0
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +43 -0
- data/lib/action_view/helpers/tags/collection_helpers.rb +83 -0
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +36 -0
- data/lib/action_view/helpers/tags/collection_select.rb +28 -0
- data/lib/action_view/helpers/tags/color_field.rb +25 -0
- data/lib/action_view/helpers/tags/date_field.rb +13 -0
- data/lib/action_view/helpers/tags/date_select.rb +72 -0
- data/lib/action_view/helpers/tags/datetime_field.rb +22 -0
- data/lib/action_view/helpers/tags/datetime_local_field.rb +19 -0
- data/lib/action_view/helpers/tags/datetime_select.rb +8 -0
- data/lib/action_view/helpers/tags/email_field.rb +8 -0
- data/lib/action_view/helpers/tags/file_field.rb +8 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +29 -0
- data/lib/action_view/helpers/tags/hidden_field.rb +8 -0
- data/lib/action_view/helpers/tags/label.rb +65 -0
- data/lib/action_view/helpers/tags/month_field.rb +13 -0
- data/lib/action_view/helpers/tags/number_field.rb +18 -0
- data/lib/action_view/helpers/tags/password_field.rb +12 -0
- data/lib/action_view/helpers/tags/radio_button.rb +31 -0
- data/lib/action_view/helpers/tags/range_field.rb +8 -0
- data/lib/action_view/helpers/tags/search_field.rb +24 -0
- data/lib/action_view/helpers/tags/select.rb +40 -0
- data/lib/action_view/helpers/tags/tel_field.rb +8 -0
- data/lib/action_view/helpers/tags/text_area.rb +18 -0
- data/lib/action_view/helpers/tags/text_field.rb +29 -0
- data/lib/action_view/helpers/tags/time_field.rb +13 -0
- data/lib/action_view/helpers/tags/time_select.rb +8 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +20 -0
- data/lib/action_view/helpers/tags/url_field.rb +8 -0
- data/lib/action_view/helpers/tags/week_field.rb +13 -0
- data/lib/action_view/helpers/tags.rb +39 -0
- data/lib/action_view/helpers/text_helper.rb +130 -114
- data/lib/action_view/helpers/translation_helper.rb +32 -16
- data/lib/action_view/helpers/url_helper.rb +211 -270
- data/lib/action_view/helpers.rb +2 -4
- data/lib/action_view/locale/en.yml +1 -105
- data/lib/action_view/log_subscriber.rb +6 -4
- data/lib/action_view/lookup_context.rb +15 -28
- data/lib/action_view/model_naming.rb +12 -0
- data/lib/action_view/path_set.rb +8 -20
- data/lib/action_view/railtie.rb +6 -22
- data/lib/action_view/record_identifier.rb +84 -0
- data/lib/action_view/renderer/abstract_renderer.rb +25 -19
- data/lib/action_view/renderer/partial_renderer.rb +158 -81
- data/lib/action_view/renderer/renderer.rb +8 -12
- data/lib/action_view/renderer/streaming_template_renderer.rb +2 -5
- data/lib/action_view/renderer/template_renderer.rb +12 -10
- data/lib/action_view/routing_url_for.rb +107 -0
- data/lib/action_view/template/error.rb +22 -12
- data/lib/action_view/template/handlers/builder.rb +1 -1
- data/lib/action_view/template/handlers/erb.rb +40 -19
- data/lib/action_view/template/handlers/raw.rb +11 -0
- data/lib/action_view/template/handlers.rb +12 -9
- data/lib/action_view/template/resolver.rb +107 -53
- data/lib/action_view/template/text.rb +12 -8
- data/lib/action_view/template/types.rb +57 -0
- data/lib/action_view/template.rb +25 -23
- data/lib/action_view/test_case.rb +67 -42
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/document.rb +0 -0
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/node.rb +12 -12
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/sanitizer.rb +13 -2
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/selector.rb +9 -9
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/tokenizer.rb +1 -1
- data/lib/{action_controller → action_view}/vendor/html-scanner/html/version.rb +0 -0
- data/lib/action_view/vendor/html-scanner.rb +20 -0
- data/lib/action_view.rb +17 -8
- metadata +184 -214
- 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/performance_test.rb +0 -1
- data/lib/action_controller/metal/compatibility.rb +0 -65
- data/lib/action_controller/metal/session_management.rb +0 -14
- data/lib/action_controller/railties/paths.rb +0 -25
- 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/testing/performance_test.rb +0 -10
- data/lib/action_view/asset_paths.rb +0 -142
- data/lib/action_view/helpers/asset_paths.rb +0 -7
- 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/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,13 +1,19 @@
|
|
|
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'
|
|
5
6
|
require 'active_support/inflector'
|
|
6
7
|
require 'action_dispatch/routing/redirection'
|
|
7
8
|
|
|
8
9
|
module ActionDispatch
|
|
9
10
|
module Routing
|
|
10
11
|
class Mapper
|
|
12
|
+
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
|
13
|
+
SCOPE_OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
|
14
|
+
:controller, :action, :path_names, :constraints,
|
|
15
|
+
:shallow, :blocks, :defaults, :options]
|
|
16
|
+
|
|
11
17
|
class Constraints #:nodoc:
|
|
12
18
|
def self.new(app, constraints, request = Rack::Request)
|
|
13
19
|
if constraints.any?
|
|
@@ -26,15 +32,10 @@ module ActionDispatch
|
|
|
26
32
|
def matches?(env)
|
|
27
33
|
req = @request.new(env)
|
|
28
34
|
|
|
29
|
-
@constraints.
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return false
|
|
34
|
-
end
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return true
|
|
35
|
+
@constraints.all? do |constraint|
|
|
36
|
+
(constraint.respond_to?(:matches?) && constraint.matches?(req)) ||
|
|
37
|
+
(constraint.respond_to?(:call) && constraint.call(*constraint_args(constraint, req)))
|
|
38
|
+
end
|
|
38
39
|
ensure
|
|
39
40
|
req.reset_parameters
|
|
40
41
|
end
|
|
@@ -50,100 +51,154 @@ module ActionDispatch
|
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
class Mapping #:nodoc:
|
|
53
|
-
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix]
|
|
54
|
+
IGNORE_OPTIONS = [:to, :as, :via, :on, :constraints, :defaults, :only, :except, :anchor, :shallow, :shallow_path, :shallow_prefix, :format]
|
|
54
55
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
|
55
56
|
WILDCARD_PATH = %r{\*([^/\)]+)\)?$}
|
|
56
57
|
|
|
58
|
+
attr_reader :scope, :path, :options, :requirements, :conditions, :defaults
|
|
59
|
+
|
|
57
60
|
def initialize(set, scope, path, options)
|
|
58
|
-
@set, @scope = set, scope
|
|
59
|
-
@
|
|
60
|
-
|
|
61
|
+
@set, @scope, @path, @options = set, scope, path, options
|
|
62
|
+
@requirements, @conditions, @defaults = {}, {}, {}
|
|
63
|
+
|
|
61
64
|
normalize_options!
|
|
65
|
+
normalize_path!
|
|
66
|
+
normalize_requirements!
|
|
67
|
+
normalize_conditions!
|
|
68
|
+
normalize_defaults!
|
|
62
69
|
end
|
|
63
70
|
|
|
64
71
|
def to_route
|
|
65
|
-
[ app, conditions, requirements, defaults,
|
|
72
|
+
[ app, conditions, requirements, defaults, options[:as], options[:anchor] ]
|
|
66
73
|
end
|
|
67
74
|
|
|
68
75
|
private
|
|
69
76
|
|
|
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]
|
|
77
|
+
def normalize_path!
|
|
78
|
+
raise ArgumentError, "path is required" if @path.blank?
|
|
79
|
+
@path = Mapper.normalize_path(@path)
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
raise ArgumentError, "Regexp multiline option not allowed in routing requirements: #{requirement.inspect}"
|
|
82
|
-
end
|
|
81
|
+
if required_format?
|
|
82
|
+
@path = "#{@path}.:format"
|
|
83
|
+
elsif optional_format?
|
|
84
|
+
@path = "#{@path}(.:format)"
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
87
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
path && (options[:to] || options[:action]).nil? && path =~ SHORTHAND_REGEX
|
|
88
|
+
def required_format?
|
|
89
|
+
options[:format] == true
|
|
89
90
|
end
|
|
90
91
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
def optional_format?
|
|
93
|
+
options[:format] != false && !path.include?(':format') && !path.end_with?('/')
|
|
94
|
+
end
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
def normalize_options!
|
|
97
|
+
@options.reverse_merge!(scope[:options]) if scope[:options]
|
|
98
|
+
path_without_format = path.sub(/\(\.:format\)$/, '')
|
|
99
|
+
|
|
100
|
+
# Add a constraint for wildcard route to make it non-greedy and match the
|
|
101
|
+
# optional format part of the route by default
|
|
102
|
+
if path_without_format.match(WILDCARD_PATH) && @options[:format] != false
|
|
103
|
+
@options[$1.to_sym] ||= /.+?/
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if path_without_format.match(':controller')
|
|
107
|
+
raise ArgumentError, ":controller segment is not allowed within a namespace block" if scope[:module]
|
|
97
108
|
|
|
98
109
|
# Add a default constraint for :controller path segments that matches namespaced
|
|
99
110
|
# controllers with default routes like :controller/:action/:id(.:format), e.g:
|
|
100
111
|
# GET /admin/products/show/1
|
|
101
|
-
# => { :
|
|
112
|
+
# => { controller: 'admin/products', action: 'show', id: '1' }
|
|
102
113
|
@options[:controller] ||= /.+?/
|
|
103
114
|
end
|
|
104
115
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
@options.merge!(default_controller_and_action)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def normalize_requirements!
|
|
120
|
+
constraints.each do |key, requirement|
|
|
121
|
+
next unless segment_keys.include?(key) || key == :controller
|
|
122
|
+
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
|
123
|
+
@requirements[key] = requirement
|
|
109
124
|
end
|
|
110
125
|
|
|
111
|
-
if
|
|
112
|
-
@
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
"#{path}.:format"
|
|
118
|
-
else
|
|
119
|
-
"#{path}(.:format)"
|
|
126
|
+
if options[:format] == true
|
|
127
|
+
@requirements[:format] ||= /.+/
|
|
128
|
+
elsif Regexp === options[:format]
|
|
129
|
+
@requirements[:format] = options[:format]
|
|
130
|
+
elsif String === options[:format]
|
|
131
|
+
@requirements[:format] = Regexp.compile(options[:format])
|
|
120
132
|
end
|
|
121
133
|
end
|
|
122
134
|
|
|
123
|
-
def
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
@set.request_class
|
|
128
|
-
)
|
|
129
|
-
end
|
|
135
|
+
def verify_regexp_requirement(requirement)
|
|
136
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
|
137
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
|
138
|
+
end
|
|
130
139
|
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
if requirement.multiline?
|
|
141
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
|
142
|
+
end
|
|
133
143
|
end
|
|
134
144
|
|
|
135
|
-
def
|
|
136
|
-
@
|
|
137
|
-
|
|
138
|
-
|
|
145
|
+
def normalize_defaults!
|
|
146
|
+
@defaults.merge!(scope[:defaults]) if scope[:defaults]
|
|
147
|
+
@defaults.merge!(options[:defaults]) if options[:defaults]
|
|
148
|
+
|
|
149
|
+
options.each do |key, default|
|
|
150
|
+
next if Regexp === default || IGNORE_OPTIONS.include?(key)
|
|
151
|
+
@defaults[key] = default
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
if options[:constraints].is_a?(Hash)
|
|
155
|
+
options[:constraints].each do |key, default|
|
|
156
|
+
next unless URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
|
|
157
|
+
@defaults[key] ||= default
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
if Regexp === options[:format]
|
|
162
|
+
@defaults[:format] = nil
|
|
163
|
+
elsif String === options[:format]
|
|
164
|
+
@defaults[:format] = options[:format]
|
|
139
165
|
end
|
|
140
166
|
end
|
|
141
167
|
|
|
142
|
-
def
|
|
143
|
-
@
|
|
144
|
-
|
|
145
|
-
|
|
168
|
+
def normalize_conditions!
|
|
169
|
+
@conditions.merge!(:path_info => path)
|
|
170
|
+
|
|
171
|
+
constraints.each do |key, condition|
|
|
172
|
+
next if segment_keys.include?(key) || key == :controller
|
|
173
|
+
@conditions[key] = condition
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
@conditions[:required_defaults] = []
|
|
177
|
+
options.each do |key, required_default|
|
|
178
|
+
next if segment_keys.include?(key) || IGNORE_OPTIONS.include?(key)
|
|
179
|
+
next if Regexp === required_default
|
|
180
|
+
@conditions[:required_defaults] << key
|
|
146
181
|
end
|
|
182
|
+
|
|
183
|
+
via_all = options.delete(:via) if options[:via] == :all
|
|
184
|
+
|
|
185
|
+
if !via_all && options[:via].blank?
|
|
186
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
|
187
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
|
188
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
|
189
|
+
" Instead of: match \"controller#action\"\n" \
|
|
190
|
+
" Do: get \"controller#action\""
|
|
191
|
+
raise msg
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
if via = options[:via]
|
|
195
|
+
list = Array(via).map { |m| m.to_s.dasherize.upcase }
|
|
196
|
+
@conditions.merge!(:request_method => list)
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def app
|
|
201
|
+
Constraints.new(endpoint, blocks, @set.request_class)
|
|
147
202
|
end
|
|
148
203
|
|
|
149
204
|
def default_controller_and_action
|
|
@@ -170,14 +225,20 @@ module ActionDispatch
|
|
|
170
225
|
controller = controller.to_s unless controller.is_a?(Regexp)
|
|
171
226
|
action = action.to_s unless action.is_a?(Regexp)
|
|
172
227
|
|
|
173
|
-
if controller.blank? && segment_keys.exclude?(
|
|
228
|
+
if controller.blank? && segment_keys.exclude?(:controller)
|
|
174
229
|
raise ArgumentError, "missing :controller"
|
|
175
230
|
end
|
|
176
231
|
|
|
177
|
-
if action.blank? && segment_keys.exclude?(
|
|
232
|
+
if action.blank? && segment_keys.exclude?(:action)
|
|
178
233
|
raise ArgumentError, "missing :action"
|
|
179
234
|
end
|
|
180
235
|
|
|
236
|
+
if controller.is_a?(String) && controller !~ /\A[a-z_0-9\/]*\z/
|
|
237
|
+
message = "'#{controller}' is not a supported controller name. This can lead to potential routing problems."
|
|
238
|
+
message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
|
|
239
|
+
raise ArgumentError, message
|
|
240
|
+
end
|
|
241
|
+
|
|
181
242
|
hash = {}
|
|
182
243
|
hash[:controller] = controller unless controller.blank?
|
|
183
244
|
hash[:action] = action unless action.blank?
|
|
@@ -186,47 +247,59 @@ module ActionDispatch
|
|
|
186
247
|
end
|
|
187
248
|
|
|
188
249
|
def blocks
|
|
189
|
-
constraints
|
|
190
|
-
|
|
191
|
-
[constraints]
|
|
250
|
+
if options[:constraints].present? && !options[:constraints].is_a?(Hash)
|
|
251
|
+
[options[:constraints]]
|
|
192
252
|
else
|
|
193
|
-
|
|
253
|
+
scope[:blocks] || []
|
|
194
254
|
end
|
|
195
255
|
end
|
|
196
256
|
|
|
197
257
|
def constraints
|
|
198
|
-
@constraints ||=
|
|
199
|
-
|
|
258
|
+
@constraints ||= {}.tap do |constraints|
|
|
259
|
+
constraints.merge!(scope[:constraints]) if scope[:constraints]
|
|
200
260
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
{ }
|
|
261
|
+
options.except(*IGNORE_OPTIONS).each do |key, option|
|
|
262
|
+
constraints[key] = option if Regexp === option
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
constraints.merge!(options[:constraints]) if options[:constraints].is_a?(Hash)
|
|
207
266
|
end
|
|
208
267
|
end
|
|
209
268
|
|
|
210
269
|
def segment_keys
|
|
211
|
-
@segment_keys ||=
|
|
212
|
-
|
|
213
|
-
|
|
270
|
+
@segment_keys ||= path_pattern.names.map{ |s| s.to_sym }
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def path_pattern
|
|
274
|
+
Journey::Path::Pattern.new(strexp)
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def strexp
|
|
278
|
+
Journey::Router::Strexp.compile(path, requirements, SEPARATORS)
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def endpoint
|
|
282
|
+
to.respond_to?(:call) ? to : dispatcher
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def dispatcher
|
|
286
|
+
Routing::RouteSet::Dispatcher.new(:defaults => defaults)
|
|
214
287
|
end
|
|
215
288
|
|
|
216
289
|
def to
|
|
217
|
-
|
|
290
|
+
options[:to]
|
|
218
291
|
end
|
|
219
292
|
|
|
220
293
|
def default_controller
|
|
221
|
-
|
|
294
|
+
options[:controller] || scope[:controller]
|
|
222
295
|
end
|
|
223
296
|
|
|
224
297
|
def default_action
|
|
225
|
-
|
|
298
|
+
options[:action] || scope[:action]
|
|
226
299
|
end
|
|
227
300
|
end
|
|
228
301
|
|
|
229
|
-
# Invokes
|
|
302
|
+
# Invokes Journey::Router::Utils.normalize_path and ensure that
|
|
230
303
|
# (:locale) becomes (/:locale) instead of /(:locale). Except
|
|
231
304
|
# for root cases, where the latter is the correct one.
|
|
232
305
|
def self.normalize_path(path)
|
|
@@ -236,21 +309,25 @@ module ActionDispatch
|
|
|
236
309
|
end
|
|
237
310
|
|
|
238
311
|
def self.normalize_name(name)
|
|
239
|
-
normalize_path(name)[1..-1].
|
|
312
|
+
normalize_path(name)[1..-1].tr("/", "_")
|
|
240
313
|
end
|
|
241
314
|
|
|
242
315
|
module Base
|
|
243
316
|
# You can specify what Rails should route "/" to with the root method:
|
|
244
317
|
#
|
|
245
|
-
# root :
|
|
318
|
+
# root to: 'pages#main'
|
|
246
319
|
#
|
|
247
320
|
# For options, see +match+, as +root+ uses it internally.
|
|
248
321
|
#
|
|
322
|
+
# You can also pass a string which will expand
|
|
323
|
+
#
|
|
324
|
+
# root 'pages#main'
|
|
325
|
+
#
|
|
249
326
|
# You should put the root route at the top of <tt>config/routes.rb</tt>,
|
|
250
327
|
# because this means it will be matched first. As this is the most popular route
|
|
251
328
|
# of most Rails applications, this is beneficial.
|
|
252
329
|
def root(options = {})
|
|
253
|
-
match '/', { :as => :root }.merge(options)
|
|
330
|
+
match '/', { :as => :root, :via => :get }.merge!(options)
|
|
254
331
|
end
|
|
255
332
|
|
|
256
333
|
# Matches a url pattern to one or more routes. Any symbols in a pattern
|
|
@@ -264,7 +341,7 @@ module ActionDispatch
|
|
|
264
341
|
# and +:action+ to the controller's action. A pattern can also map
|
|
265
342
|
# wildcard segments (globs) to params:
|
|
266
343
|
#
|
|
267
|
-
# match 'songs/*category/:title'
|
|
344
|
+
# match 'songs/*category/:title', to: 'songs#show'
|
|
268
345
|
#
|
|
269
346
|
# # 'songs/rock/classic/stairway-to-heaven' sets
|
|
270
347
|
# # params[:category] = 'rock/classic'
|
|
@@ -274,16 +351,20 @@ module ActionDispatch
|
|
|
274
351
|
# +:controller+ should be set in options or hash shorthand. Examples:
|
|
275
352
|
#
|
|
276
353
|
# match 'photos/:id' => 'photos#show'
|
|
277
|
-
# match 'photos/:id', :
|
|
278
|
-
# match 'photos/:id', :
|
|
354
|
+
# match 'photos/:id', to: 'photos#show'
|
|
355
|
+
# match 'photos/:id', controller: 'photos', action: 'show'
|
|
279
356
|
#
|
|
280
357
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
|
281
358
|
# responds to +call+:
|
|
282
359
|
#
|
|
283
|
-
# match 'photos/:id'
|
|
284
|
-
# match 'photos/:id'
|
|
360
|
+
# match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }
|
|
361
|
+
# match 'photos/:id', to: PhotoRackApp
|
|
285
362
|
# # Yes, controller actions are just rack endpoints
|
|
286
|
-
# match 'photos/:id'
|
|
363
|
+
# match 'photos/:id', to: PhotosController.action(:show)
|
|
364
|
+
#
|
|
365
|
+
# Because request various HTTP verbs with a single action has security
|
|
366
|
+
# implications, is recommendable use HttpHelpers[rdoc-ref:HttpHelpers]
|
|
367
|
+
# instead +match+
|
|
287
368
|
#
|
|
288
369
|
# === Options
|
|
289
370
|
#
|
|
@@ -301,7 +382,7 @@ module ActionDispatch
|
|
|
301
382
|
# [:module]
|
|
302
383
|
# The namespace for :controller.
|
|
303
384
|
#
|
|
304
|
-
# match 'path'
|
|
385
|
+
# match 'path', to: 'c#a', module: 'sekret', controller: 'posts'
|
|
305
386
|
# #=> Sekret::PostsController
|
|
306
387
|
#
|
|
307
388
|
# See <tt>Scoping#namespace</tt> for its scope equivalent.
|
|
@@ -312,16 +393,17 @@ module ActionDispatch
|
|
|
312
393
|
# [:via]
|
|
313
394
|
# Allowed HTTP verb(s) for route.
|
|
314
395
|
#
|
|
315
|
-
# match 'path'
|
|
316
|
-
# match 'path'
|
|
396
|
+
# match 'path', to: 'c#a', via: :get
|
|
397
|
+
# match 'path', to: 'c#a', via: [:get, :post]
|
|
398
|
+
# match 'path', to: 'c#a', via: :all
|
|
317
399
|
#
|
|
318
400
|
# [:to]
|
|
319
401
|
# Points to a +Rack+ endpoint. Can be an object that responds to
|
|
320
402
|
# +call+ or a string representing a controller's action.
|
|
321
403
|
#
|
|
322
|
-
# match 'path', :
|
|
323
|
-
# match 'path', :
|
|
324
|
-
# match 'path', :
|
|
404
|
+
# match 'path', to: 'controller#action'
|
|
405
|
+
# match 'path', to: lambda { |env| [200, {}, ["Success!"]] }
|
|
406
|
+
# match 'path', to: RackApp
|
|
325
407
|
#
|
|
326
408
|
# [:on]
|
|
327
409
|
# Shorthand for wrapping routes in a specific RESTful context. Valid
|
|
@@ -329,27 +411,31 @@ module ActionDispatch
|
|
|
329
411
|
# <tt>resource(s)</tt> block. For example:
|
|
330
412
|
#
|
|
331
413
|
# resource :bar do
|
|
332
|
-
# match 'foo'
|
|
414
|
+
# match 'foo', to: 'c#a', on: :member, via: [:get, :post]
|
|
333
415
|
# end
|
|
334
416
|
#
|
|
335
417
|
# Is equivalent to:
|
|
336
418
|
#
|
|
337
419
|
# resource :bar do
|
|
338
420
|
# member do
|
|
339
|
-
# match 'foo'
|
|
421
|
+
# match 'foo', to: 'c#a', via: [:get, :post]
|
|
340
422
|
# end
|
|
341
423
|
# end
|
|
342
424
|
#
|
|
343
425
|
# [:constraints]
|
|
344
|
-
# Constrains parameters with a hash of regular expressions
|
|
345
|
-
# object that responds to <tt>matches?</tt
|
|
426
|
+
# Constrains parameters with a hash of regular expressions
|
|
427
|
+
# or an object that responds to <tt>matches?</tt>. In addition, constraints
|
|
428
|
+
# other than path can also be specified with any object
|
|
429
|
+
# that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
|
|
346
430
|
#
|
|
347
|
-
# match 'path/:id', :
|
|
431
|
+
# match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }
|
|
432
|
+
#
|
|
433
|
+
# match 'json_only', constraints: { format: 'json' }
|
|
348
434
|
#
|
|
349
435
|
# class Blacklist
|
|
350
436
|
# def matches?(request) request.remote_ip == '1.2.3.4' end
|
|
351
437
|
# end
|
|
352
|
-
# match 'path'
|
|
438
|
+
# match 'path', to: 'c#a', constraints: Blacklist.new
|
|
353
439
|
#
|
|
354
440
|
# See <tt>Scoping#constraints</tt> for more examples with its scope
|
|
355
441
|
# equivalent.
|
|
@@ -358,7 +444,7 @@ module ActionDispatch
|
|
|
358
444
|
# Sets defaults for parameters
|
|
359
445
|
#
|
|
360
446
|
# # Sets params[:format] to 'jpg' by default
|
|
361
|
-
# match 'path'
|
|
447
|
+
# match 'path', to: 'c#a', defaults: { format: 'jpg' }
|
|
362
448
|
#
|
|
363
449
|
# See <tt>Scoping#defaults</tt> for its scope equivalent.
|
|
364
450
|
#
|
|
@@ -367,13 +453,17 @@ module ActionDispatch
|
|
|
367
453
|
# false, the pattern matches any request prefixed with the given path.
|
|
368
454
|
#
|
|
369
455
|
# # Matches any request starting with 'path'
|
|
370
|
-
# match 'path'
|
|
456
|
+
# match 'path', to: 'c#a', anchor: false
|
|
457
|
+
#
|
|
458
|
+
# [:format]
|
|
459
|
+
# Allows you to specify the default value for optional +format+
|
|
460
|
+
# segment or disable it by supplying +false+.
|
|
371
461
|
def match(path, options=nil)
|
|
372
462
|
end
|
|
373
463
|
|
|
374
464
|
# Mount a Rack-based application to be used within the application.
|
|
375
465
|
#
|
|
376
|
-
# mount SomeRackApp, :
|
|
466
|
+
# mount SomeRackApp, at: "some_route"
|
|
377
467
|
#
|
|
378
468
|
# Alternatively:
|
|
379
469
|
#
|
|
@@ -386,7 +476,7 @@ module ActionDispatch
|
|
|
386
476
|
# the helper is either +some_rack_app_path+ or +some_rack_app_url+.
|
|
387
477
|
# To customize this helper's name, use the +:as+ option:
|
|
388
478
|
#
|
|
389
|
-
# mount(SomeRackApp => "some_route", :
|
|
479
|
+
# mount(SomeRackApp => "some_route", as: "exciting")
|
|
390
480
|
#
|
|
391
481
|
# This will generate the +exciting_path+ and +exciting_url+ helpers
|
|
392
482
|
# which can be used to navigate to this mounted app.
|
|
@@ -394,14 +484,19 @@ module ActionDispatch
|
|
|
394
484
|
if options
|
|
395
485
|
path = options.delete(:at)
|
|
396
486
|
else
|
|
487
|
+
unless Hash === app
|
|
488
|
+
raise ArgumentError, "must be called with mount point"
|
|
489
|
+
end
|
|
490
|
+
|
|
397
491
|
options = app
|
|
398
|
-
app, path = options.find { |k,
|
|
492
|
+
app, path = options.find { |k, _| k.respond_to?(:call) }
|
|
399
493
|
options.delete(app) if app
|
|
400
494
|
end
|
|
401
495
|
|
|
402
496
|
raise "A rack application must be specified" unless path
|
|
403
497
|
|
|
404
|
-
options[:as]
|
|
498
|
+
options[:as] ||= app_name(app)
|
|
499
|
+
options[:via] ||= :all
|
|
405
500
|
|
|
406
501
|
match(path, options.merge(:to => app, :anchor => false, :format => false))
|
|
407
502
|
|
|
@@ -428,7 +523,7 @@ module ActionDispatch
|
|
|
428
523
|
app.railtie_name
|
|
429
524
|
else
|
|
430
525
|
class_name = app.class.is_a?(Class) ? app.name : app.class.name
|
|
431
|
-
ActiveSupport::Inflector.underscore(class_name).
|
|
526
|
+
ActiveSupport::Inflector.underscore(class_name).tr("/", "_")
|
|
432
527
|
end
|
|
433
528
|
end
|
|
434
529
|
|
|
@@ -438,14 +533,16 @@ module ActionDispatch
|
|
|
438
533
|
_route = @set.named_routes.routes[name.to_sym]
|
|
439
534
|
_routes = @set
|
|
440
535
|
app.routes.define_mounted_helper(name)
|
|
441
|
-
app.routes.class_eval do
|
|
536
|
+
app.routes.singleton_class.class_eval do
|
|
537
|
+
define_method :mounted? do
|
|
538
|
+
true
|
|
539
|
+
end
|
|
540
|
+
|
|
442
541
|
define_method :_generate_prefix do |options|
|
|
443
542
|
prefix_options = options.slice(*_route.segment_keys)
|
|
444
543
|
# we must actually delete prefix segment keys to avoid passing them to next url_for
|
|
445
544
|
_route.segment_keys.each { |k| options.delete(k) }
|
|
446
|
-
|
|
447
|
-
prefix = prefix.gsub(%r{/\z}, '')
|
|
448
|
-
prefix
|
|
545
|
+
_routes.url_helpers.send("#{name}_path", prefix_options)
|
|
449
546
|
end
|
|
450
547
|
end
|
|
451
548
|
end
|
|
@@ -453,51 +550,50 @@ module ActionDispatch
|
|
|
453
550
|
|
|
454
551
|
module HttpHelpers
|
|
455
552
|
# Define a route that only recognizes HTTP GET.
|
|
456
|
-
# For supported arguments, see
|
|
457
|
-
#
|
|
458
|
-
# Example:
|
|
553
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
|
459
554
|
#
|
|
460
|
-
#
|
|
555
|
+
# get 'bacon', to: 'food#bacon'
|
|
461
556
|
def get(*args, &block)
|
|
462
|
-
map_method(:get,
|
|
557
|
+
map_method(:get, args, &block)
|
|
463
558
|
end
|
|
464
559
|
|
|
465
560
|
# Define a route that only recognizes HTTP POST.
|
|
466
|
-
# For supported arguments, see
|
|
467
|
-
#
|
|
468
|
-
# Example:
|
|
561
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
|
469
562
|
#
|
|
470
|
-
#
|
|
563
|
+
# post 'bacon', to: 'food#bacon'
|
|
471
564
|
def post(*args, &block)
|
|
472
|
-
map_method(:post,
|
|
565
|
+
map_method(:post, args, &block)
|
|
473
566
|
end
|
|
474
567
|
|
|
475
|
-
# Define a route that only recognizes HTTP
|
|
476
|
-
# For supported arguments, see
|
|
477
|
-
#
|
|
478
|
-
# Example:
|
|
568
|
+
# Define a route that only recognizes HTTP PATCH.
|
|
569
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
|
479
570
|
#
|
|
480
|
-
#
|
|
481
|
-
def
|
|
482
|
-
map_method(:
|
|
571
|
+
# patch 'bacon', to: 'food#bacon'
|
|
572
|
+
def patch(*args, &block)
|
|
573
|
+
map_method(:patch, args, &block)
|
|
483
574
|
end
|
|
484
575
|
|
|
485
576
|
# Define a route that only recognizes HTTP PUT.
|
|
486
|
-
# For supported arguments, see
|
|
577
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
|
487
578
|
#
|
|
488
|
-
#
|
|
579
|
+
# put 'bacon', to: 'food#bacon'
|
|
580
|
+
def put(*args, &block)
|
|
581
|
+
map_method(:put, args, &block)
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
# Define a route that only recognizes HTTP DELETE.
|
|
585
|
+
# For supported arguments, see match[rdoc-ref:Base#match]
|
|
489
586
|
#
|
|
490
|
-
#
|
|
587
|
+
# delete 'broccoli', to: 'food#broccoli'
|
|
491
588
|
def delete(*args, &block)
|
|
492
|
-
map_method(:delete,
|
|
589
|
+
map_method(:delete, args, &block)
|
|
493
590
|
end
|
|
494
591
|
|
|
495
592
|
private
|
|
496
|
-
def map_method(method,
|
|
593
|
+
def map_method(method, args, &block)
|
|
497
594
|
options = args.extract_options!
|
|
498
595
|
options[:via] = method
|
|
499
|
-
args
|
|
500
|
-
match(*args, &block)
|
|
596
|
+
match(*args, options, &block)
|
|
501
597
|
self
|
|
502
598
|
end
|
|
503
599
|
end
|
|
@@ -515,24 +611,24 @@ module ActionDispatch
|
|
|
515
611
|
# This will create a number of routes for each of the posts and comments
|
|
516
612
|
# controller. For <tt>Admin::PostsController</tt>, Rails will create:
|
|
517
613
|
#
|
|
518
|
-
# GET
|
|
519
|
-
# GET
|
|
520
|
-
# POST
|
|
521
|
-
# GET
|
|
522
|
-
# GET
|
|
523
|
-
# PUT
|
|
524
|
-
# DELETE
|
|
614
|
+
# GET /admin/posts
|
|
615
|
+
# GET /admin/posts/new
|
|
616
|
+
# POST /admin/posts
|
|
617
|
+
# GET /admin/posts/1
|
|
618
|
+
# GET /admin/posts/1/edit
|
|
619
|
+
# PATCH/PUT /admin/posts/1
|
|
620
|
+
# DELETE /admin/posts/1
|
|
525
621
|
#
|
|
526
622
|
# If you want to route /posts (without the prefix /admin) to
|
|
527
623
|
# <tt>Admin::PostsController</tt>, you could use
|
|
528
624
|
#
|
|
529
|
-
# scope :
|
|
625
|
+
# scope module: "admin" do
|
|
530
626
|
# resources :posts
|
|
531
627
|
# end
|
|
532
628
|
#
|
|
533
629
|
# or, for a single case
|
|
534
630
|
#
|
|
535
|
-
# resources :posts, :
|
|
631
|
+
# resources :posts, module: "admin"
|
|
536
632
|
#
|
|
537
633
|
# If you want to route /admin/posts to +PostsController+
|
|
538
634
|
# (without the Admin:: module prefix), you could use
|
|
@@ -543,25 +639,25 @@ module ActionDispatch
|
|
|
543
639
|
#
|
|
544
640
|
# or, for a single case
|
|
545
641
|
#
|
|
546
|
-
# resources :posts, :
|
|
642
|
+
# resources :posts, path: "/admin/posts"
|
|
547
643
|
#
|
|
548
644
|
# In each of these cases, the named routes remain the same as if you did
|
|
549
645
|
# not use scope. In the last case, the following paths map to
|
|
550
646
|
# +PostsController+:
|
|
551
647
|
#
|
|
552
|
-
# GET
|
|
553
|
-
# GET
|
|
554
|
-
# POST
|
|
555
|
-
# GET
|
|
556
|
-
# GET
|
|
557
|
-
# PUT
|
|
558
|
-
# DELETE
|
|
648
|
+
# GET /admin/posts
|
|
649
|
+
# GET /admin/posts/new
|
|
650
|
+
# POST /admin/posts
|
|
651
|
+
# GET /admin/posts/1
|
|
652
|
+
# GET /admin/posts/1/edit
|
|
653
|
+
# PATCH/PUT /admin/posts/1
|
|
654
|
+
# DELETE /admin/posts/1
|
|
559
655
|
module Scoping
|
|
560
656
|
# Scopes a set of routes to the given default options.
|
|
561
657
|
#
|
|
562
658
|
# Take the following route definition as an example:
|
|
563
659
|
#
|
|
564
|
-
# scope :
|
|
660
|
+
# scope path: ":account_id", as: "account" do
|
|
565
661
|
# resources :projects
|
|
566
662
|
# end
|
|
567
663
|
#
|
|
@@ -573,63 +669,62 @@ module ActionDispatch
|
|
|
573
669
|
#
|
|
574
670
|
# Takes same options as <tt>Base#match</tt> and <tt>Resources#resources</tt>.
|
|
575
671
|
#
|
|
576
|
-
# === Examples
|
|
577
|
-
#
|
|
578
672
|
# # route /posts (without the prefix /admin) to <tt>Admin::PostsController</tt>
|
|
579
|
-
# scope :
|
|
673
|
+
# scope module: "admin" do
|
|
580
674
|
# resources :posts
|
|
581
675
|
# end
|
|
582
676
|
#
|
|
583
677
|
# # prefix the posts resource's requests with '/admin'
|
|
584
|
-
# scope :
|
|
678
|
+
# scope path: "/admin" do
|
|
585
679
|
# resources :posts
|
|
586
680
|
# end
|
|
587
681
|
#
|
|
588
682
|
# # prefix the routing helper name: +sekret_posts_path+ instead of +posts_path+
|
|
589
|
-
# scope :
|
|
683
|
+
# scope as: "sekret" do
|
|
590
684
|
# resources :posts
|
|
591
685
|
# end
|
|
592
686
|
def scope(*args)
|
|
593
|
-
options = args.extract_options
|
|
594
|
-
options = options.dup
|
|
595
|
-
|
|
596
|
-
options[:path] = args.first if args.first.is_a?(String)
|
|
687
|
+
options = args.extract_options!.dup
|
|
597
688
|
recover = {}
|
|
598
689
|
|
|
690
|
+
options[:path] = args.flatten.join('/') if args.any?
|
|
599
691
|
options[:constraints] ||= {}
|
|
600
|
-
|
|
692
|
+
|
|
693
|
+
if options[:constraints].is_a?(Hash)
|
|
694
|
+
defaults = options[:constraints].select do
|
|
695
|
+
|k, v| URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
|
|
696
|
+
end
|
|
697
|
+
|
|
698
|
+
(options[:defaults] ||= {}).reverse_merge!(defaults)
|
|
699
|
+
else
|
|
601
700
|
block, options[:constraints] = options[:constraints], {}
|
|
602
701
|
end
|
|
603
702
|
|
|
604
|
-
|
|
605
|
-
if
|
|
703
|
+
SCOPE_OPTIONS.each do |option|
|
|
704
|
+
if option == :blocks
|
|
705
|
+
value = block
|
|
706
|
+
elsif option == :options
|
|
707
|
+
value = options
|
|
708
|
+
else
|
|
709
|
+
value = options.delete(option)
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
if value
|
|
606
713
|
recover[option] = @scope[option]
|
|
607
714
|
@scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
|
608
715
|
end
|
|
609
716
|
end
|
|
610
717
|
|
|
611
|
-
recover[:block] = @scope[:blocks]
|
|
612
|
-
@scope[:blocks] = merge_blocks_scope(@scope[:blocks], block)
|
|
613
|
-
|
|
614
|
-
recover[:options] = @scope[:options]
|
|
615
|
-
@scope[:options] = merge_options_scope(@scope[:options], options)
|
|
616
|
-
|
|
617
718
|
yield
|
|
618
719
|
self
|
|
619
720
|
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]
|
|
721
|
+
@scope.merge!(recover)
|
|
626
722
|
end
|
|
627
723
|
|
|
628
724
|
# Scopes routes to a specific controller
|
|
629
725
|
#
|
|
630
|
-
# Example:
|
|
631
726
|
# controller "food" do
|
|
632
|
-
# match "bacon", :
|
|
727
|
+
# match "bacon", action: "bacon"
|
|
633
728
|
# end
|
|
634
729
|
def controller(controller, options={})
|
|
635
730
|
options[:controller] = controller
|
|
@@ -644,13 +739,13 @@ module ActionDispatch
|
|
|
644
739
|
#
|
|
645
740
|
# This generates the following routes:
|
|
646
741
|
#
|
|
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
|
|
742
|
+
# admin_posts GET /admin/posts(.:format) admin/posts#index
|
|
743
|
+
# admin_posts POST /admin/posts(.:format) admin/posts#create
|
|
744
|
+
# new_admin_post GET /admin/posts/new(.:format) admin/posts#new
|
|
745
|
+
# edit_admin_post GET /admin/posts/:id/edit(.:format) admin/posts#edit
|
|
746
|
+
# admin_post GET /admin/posts/:id(.:format) admin/posts#show
|
|
747
|
+
# admin_post PATCH/PUT /admin/posts/:id(.:format) admin/posts#update
|
|
748
|
+
# admin_post DELETE /admin/posts/:id(.:format) admin/posts#destroy
|
|
654
749
|
#
|
|
655
750
|
# === Options
|
|
656
751
|
#
|
|
@@ -660,20 +755,18 @@ module ActionDispatch
|
|
|
660
755
|
# For options, see <tt>Base#match</tt>. For +:shallow_path+ option, see
|
|
661
756
|
# <tt>Resources#resources</tt>.
|
|
662
757
|
#
|
|
663
|
-
# === Examples
|
|
664
|
-
#
|
|
665
758
|
# # accessible through /sekret/posts rather than /admin/posts
|
|
666
|
-
# namespace :admin, :
|
|
759
|
+
# namespace :admin, path: "sekret" do
|
|
667
760
|
# resources :posts
|
|
668
761
|
# end
|
|
669
762
|
#
|
|
670
763
|
# # maps to <tt>Sekret::PostsController</tt> rather than <tt>Admin::PostsController</tt>
|
|
671
|
-
# namespace :admin, :
|
|
764
|
+
# namespace :admin, module: "sekret" do
|
|
672
765
|
# resources :posts
|
|
673
766
|
# end
|
|
674
767
|
#
|
|
675
768
|
# # generates +sekret_posts_path+ rather than +admin_posts_path+
|
|
676
|
-
# namespace :admin, :
|
|
769
|
+
# namespace :admin, as: "sekret" do
|
|
677
770
|
# resources :posts
|
|
678
771
|
# end
|
|
679
772
|
def namespace(path, options = {})
|
|
@@ -687,7 +780,7 @@ module ActionDispatch
|
|
|
687
780
|
# Allows you to constrain the nested routes based on a set of rules.
|
|
688
781
|
# For instance, in order to change the routes to allow for a dot character in the +id+ parameter:
|
|
689
782
|
#
|
|
690
|
-
# constraints(:
|
|
783
|
+
# constraints(id: /\d+\.\d+/) do
|
|
691
784
|
# resources :posts
|
|
692
785
|
# end
|
|
693
786
|
#
|
|
@@ -697,7 +790,7 @@ module ActionDispatch
|
|
|
697
790
|
# You may use this to also restrict other parameters:
|
|
698
791
|
#
|
|
699
792
|
# resources :posts do
|
|
700
|
-
# constraints(:
|
|
793
|
+
# constraints(post_id: /\d+\.\d+/) do
|
|
701
794
|
# resources :comments
|
|
702
795
|
# end
|
|
703
796
|
# end
|
|
@@ -706,7 +799,7 @@ module ActionDispatch
|
|
|
706
799
|
#
|
|
707
800
|
# Routes can also be constrained to an IP or a certain range of IP addresses:
|
|
708
801
|
#
|
|
709
|
-
# constraints(:
|
|
802
|
+
# constraints(ip: /192\.168\.\d+\.\d+/) do
|
|
710
803
|
# resources :posts
|
|
711
804
|
# end
|
|
712
805
|
#
|
|
@@ -743,8 +836,8 @@ module ActionDispatch
|
|
|
743
836
|
end
|
|
744
837
|
|
|
745
838
|
# Allows you to set default parameters for a route, such as this:
|
|
746
|
-
# defaults :
|
|
747
|
-
# match 'scoped_pages/(:id)', :
|
|
839
|
+
# defaults id: 'home' do
|
|
840
|
+
# match 'scoped_pages/(:id)', to: 'pages#show'
|
|
748
841
|
# end
|
|
749
842
|
# Using this, the +:id+ parameter here will default to 'home'.
|
|
750
843
|
def defaults(defaults = {})
|
|
@@ -752,10 +845,6 @@ module ActionDispatch
|
|
|
752
845
|
end
|
|
753
846
|
|
|
754
847
|
private
|
|
755
|
-
def scope_options #:nodoc:
|
|
756
|
-
@scope_options ||= private_methods.grep(/^merge_(.+)_scope$/) { $1.to_sym }
|
|
757
|
-
end
|
|
758
|
-
|
|
759
848
|
def merge_path_scope(parent, child) #:nodoc:
|
|
760
849
|
Mapper.normalize_path("#{parent}/#{child}")
|
|
761
850
|
end
|
|
@@ -803,7 +892,7 @@ module ActionDispatch
|
|
|
803
892
|
end
|
|
804
893
|
|
|
805
894
|
def merge_options_scope(parent, child) #:nodoc:
|
|
806
|
-
(parent || {}).except(*override_keys(child)).merge(child)
|
|
895
|
+
(parent || {}).except(*override_keys(child)).merge!(child)
|
|
807
896
|
end
|
|
808
897
|
|
|
809
898
|
def merge_shallow_scope(parent, child) #:nodoc:
|
|
@@ -850,7 +939,7 @@ module ActionDispatch
|
|
|
850
939
|
# use dots as part of the +:id+ parameter add a constraint which
|
|
851
940
|
# overrides this restriction, e.g:
|
|
852
941
|
#
|
|
853
|
-
# resources :articles, :
|
|
942
|
+
# resources :articles, id: /[^\/]+/
|
|
854
943
|
#
|
|
855
944
|
# This allows any character other than a slash as part of your +:id+.
|
|
856
945
|
#
|
|
@@ -858,17 +947,20 @@ module ActionDispatch
|
|
|
858
947
|
# CANONICAL_ACTIONS holds all actions that does not need a prefix or
|
|
859
948
|
# a path appended since they fit properly in their scope level.
|
|
860
949
|
VALID_ON_OPTIONS = [:new, :collection, :member]
|
|
861
|
-
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except]
|
|
950
|
+
RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
|
|
862
951
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
|
952
|
+
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
|
953
|
+
RESOURCE_SCOPES = [:resource, :resources]
|
|
863
954
|
|
|
864
955
|
class Resource #:nodoc:
|
|
865
|
-
attr_reader :controller, :path, :options
|
|
956
|
+
attr_reader :controller, :path, :options, :param
|
|
866
957
|
|
|
867
958
|
def initialize(entities, options = {})
|
|
868
959
|
@name = entities.to_s
|
|
869
960
|
@path = (options[:path] || @name).to_s
|
|
870
961
|
@controller = (options[:controller] || @name).to_s
|
|
871
962
|
@as = options[:as]
|
|
963
|
+
@param = (options[:param] || :id).to_sym
|
|
872
964
|
@options = options
|
|
873
965
|
end
|
|
874
966
|
|
|
@@ -913,15 +1005,21 @@ module ActionDispatch
|
|
|
913
1005
|
alias :collection_scope :path
|
|
914
1006
|
|
|
915
1007
|
def member_scope
|
|
916
|
-
"#{path}
|
|
1008
|
+
"#{path}/:#{param}"
|
|
917
1009
|
end
|
|
918
1010
|
|
|
1011
|
+
alias :shallow_scope :member_scope
|
|
1012
|
+
|
|
919
1013
|
def new_scope(new_path)
|
|
920
1014
|
"#{path}/#{new_path}"
|
|
921
1015
|
end
|
|
922
1016
|
|
|
1017
|
+
def nested_param
|
|
1018
|
+
:"#{singular}_#{param}"
|
|
1019
|
+
end
|
|
1020
|
+
|
|
923
1021
|
def nested_scope
|
|
924
|
-
"#{path}/:#{
|
|
1022
|
+
"#{path}/:#{nested_param}"
|
|
925
1023
|
end
|
|
926
1024
|
|
|
927
1025
|
end
|
|
@@ -969,12 +1067,12 @@ module ActionDispatch
|
|
|
969
1067
|
# the +GeoCoders+ controller (note that the controller is named after
|
|
970
1068
|
# the plural):
|
|
971
1069
|
#
|
|
972
|
-
# GET
|
|
973
|
-
# POST
|
|
974
|
-
# GET
|
|
975
|
-
# GET
|
|
976
|
-
# PUT
|
|
977
|
-
# DELETE
|
|
1070
|
+
# GET /geocoder/new
|
|
1071
|
+
# POST /geocoder
|
|
1072
|
+
# GET /geocoder
|
|
1073
|
+
# GET /geocoder/edit
|
|
1074
|
+
# PATCH/PUT /geocoder
|
|
1075
|
+
# DELETE /geocoder
|
|
978
1076
|
#
|
|
979
1077
|
# === Options
|
|
980
1078
|
# Takes same options as +resources+.
|
|
@@ -988,6 +1086,8 @@ module ActionDispatch
|
|
|
988
1086
|
resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
|
|
989
1087
|
yield if block_given?
|
|
990
1088
|
|
|
1089
|
+
concerns(options[:concerns]) if options[:concerns]
|
|
1090
|
+
|
|
991
1091
|
collection do
|
|
992
1092
|
post :create
|
|
993
1093
|
end if parent_resource.actions.include?(:create)
|
|
@@ -996,12 +1096,7 @@ module ActionDispatch
|
|
|
996
1096
|
get :new
|
|
997
1097
|
end if parent_resource.actions.include?(:new)
|
|
998
1098
|
|
|
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
|
|
1099
|
+
set_member_mappings_for_resource
|
|
1005
1100
|
end
|
|
1006
1101
|
|
|
1007
1102
|
self
|
|
@@ -1017,13 +1112,13 @@ module ActionDispatch
|
|
|
1017
1112
|
# creates seven different routes in your application, all mapping to
|
|
1018
1113
|
# the +Photos+ controller:
|
|
1019
1114
|
#
|
|
1020
|
-
# GET
|
|
1021
|
-
# GET
|
|
1022
|
-
# POST
|
|
1023
|
-
# GET
|
|
1024
|
-
# GET
|
|
1025
|
-
# PUT
|
|
1026
|
-
# DELETE
|
|
1115
|
+
# GET /photos
|
|
1116
|
+
# GET /photos/new
|
|
1117
|
+
# POST /photos
|
|
1118
|
+
# GET /photos/:id
|
|
1119
|
+
# GET /photos/:id/edit
|
|
1120
|
+
# PATCH/PUT /photos/:id
|
|
1121
|
+
# DELETE /photos/:id
|
|
1027
1122
|
#
|
|
1028
1123
|
# Resources can also be nested infinitely by using this block syntax:
|
|
1029
1124
|
#
|
|
@@ -1033,13 +1128,13 @@ module ActionDispatch
|
|
|
1033
1128
|
#
|
|
1034
1129
|
# This generates the following comments routes:
|
|
1035
1130
|
#
|
|
1036
|
-
# GET
|
|
1037
|
-
# GET
|
|
1038
|
-
# POST
|
|
1039
|
-
# GET
|
|
1040
|
-
# GET
|
|
1041
|
-
# PUT
|
|
1042
|
-
# DELETE
|
|
1131
|
+
# GET /photos/:photo_id/comments
|
|
1132
|
+
# GET /photos/:photo_id/comments/new
|
|
1133
|
+
# POST /photos/:photo_id/comments
|
|
1134
|
+
# GET /photos/:photo_id/comments/:id
|
|
1135
|
+
# GET /photos/:photo_id/comments/:id/edit
|
|
1136
|
+
# PATCH/PUT /photos/:photo_id/comments/:id
|
|
1137
|
+
# DELETE /photos/:photo_id/comments/:id
|
|
1043
1138
|
#
|
|
1044
1139
|
# === Options
|
|
1045
1140
|
# Takes same options as <tt>Base#match</tt> as well as:
|
|
@@ -1048,43 +1143,43 @@ module ActionDispatch
|
|
|
1048
1143
|
# Allows you to change the segment component of the +edit+ and +new+ actions.
|
|
1049
1144
|
# Actions not specified are not changed.
|
|
1050
1145
|
#
|
|
1051
|
-
# resources :posts, :
|
|
1146
|
+
# resources :posts, path_names: { new: "brand_new" }
|
|
1052
1147
|
#
|
|
1053
1148
|
# The above example will now change /posts/new to /posts/brand_new
|
|
1054
1149
|
#
|
|
1055
1150
|
# [:path]
|
|
1056
1151
|
# Allows you to change the path prefix for the resource.
|
|
1057
1152
|
#
|
|
1058
|
-
# resources :posts, :
|
|
1153
|
+
# resources :posts, path: 'postings'
|
|
1059
1154
|
#
|
|
1060
1155
|
# The resource and all segments will now route to /postings instead of /posts
|
|
1061
1156
|
#
|
|
1062
1157
|
# [:only]
|
|
1063
1158
|
# Only generate routes for the given actions.
|
|
1064
1159
|
#
|
|
1065
|
-
# resources :cows, :
|
|
1066
|
-
# resources :cows, :
|
|
1160
|
+
# resources :cows, only: :show
|
|
1161
|
+
# resources :cows, only: [:show, :index]
|
|
1067
1162
|
#
|
|
1068
1163
|
# [:except]
|
|
1069
1164
|
# Generate all routes except for the given actions.
|
|
1070
1165
|
#
|
|
1071
|
-
# resources :cows, :
|
|
1072
|
-
# resources :cows, :
|
|
1166
|
+
# resources :cows, except: :show
|
|
1167
|
+
# resources :cows, except: [:show, :index]
|
|
1073
1168
|
#
|
|
1074
1169
|
# [:shallow]
|
|
1075
1170
|
# Generates shallow routes for nested resource(s). When placed on a parent resource,
|
|
1076
1171
|
# generates shallow routes for all nested resources.
|
|
1077
1172
|
#
|
|
1078
|
-
# resources :posts, :
|
|
1173
|
+
# resources :posts, shallow: true do
|
|
1079
1174
|
# resources :comments
|
|
1080
1175
|
# end
|
|
1081
1176
|
#
|
|
1082
1177
|
# Is the same as:
|
|
1083
1178
|
#
|
|
1084
1179
|
# resources :posts do
|
|
1085
|
-
# resources :comments, :
|
|
1180
|
+
# resources :comments, except: [:show, :edit, :update, :destroy]
|
|
1086
1181
|
# end
|
|
1087
|
-
# resources :comments, :
|
|
1182
|
+
# resources :comments, only: [:show, :edit, :update, :destroy]
|
|
1088
1183
|
#
|
|
1089
1184
|
# This allows URLs for resources that otherwise would be deeply nested such
|
|
1090
1185
|
# as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
|
|
@@ -1093,29 +1188,52 @@ module ActionDispatch
|
|
|
1093
1188
|
# [:shallow_path]
|
|
1094
1189
|
# Prefixes nested shallow routes with the specified path.
|
|
1095
1190
|
#
|
|
1096
|
-
# scope :
|
|
1191
|
+
# scope shallow_path: "sekret" do
|
|
1192
|
+
# resources :posts do
|
|
1193
|
+
# resources :comments, shallow: true
|
|
1194
|
+
# end
|
|
1195
|
+
# end
|
|
1196
|
+
#
|
|
1197
|
+
# The +comments+ resource here will have the following routes generated for it:
|
|
1198
|
+
#
|
|
1199
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
|
1200
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
|
1201
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
|
1202
|
+
# edit_comment GET /sekret/comments/:id/edit(.:format)
|
|
1203
|
+
# comment GET /sekret/comments/:id(.:format)
|
|
1204
|
+
# comment PATCH/PUT /sekret/comments/:id(.:format)
|
|
1205
|
+
# comment DELETE /sekret/comments/:id(.:format)
|
|
1206
|
+
#
|
|
1207
|
+
# [:shallow_prefix]
|
|
1208
|
+
# Prefixes nested shallow route names with specified prefix.
|
|
1209
|
+
#
|
|
1210
|
+
# scope shallow_prefix: "sekret" do
|
|
1097
1211
|
# resources :posts do
|
|
1098
|
-
# resources :comments, :
|
|
1212
|
+
# resources :comments, shallow: true
|
|
1099
1213
|
# end
|
|
1100
1214
|
# end
|
|
1101
1215
|
#
|
|
1102
1216
|
# The +comments+ resource here will have the following routes generated for it:
|
|
1103
1217
|
#
|
|
1104
|
-
# post_comments
|
|
1105
|
-
# post_comments
|
|
1106
|
-
# new_post_comment
|
|
1107
|
-
#
|
|
1108
|
-
#
|
|
1109
|
-
#
|
|
1110
|
-
#
|
|
1218
|
+
# post_comments GET /posts/:post_id/comments(.:format)
|
|
1219
|
+
# post_comments POST /posts/:post_id/comments(.:format)
|
|
1220
|
+
# new_post_comment GET /posts/:post_id/comments/new(.:format)
|
|
1221
|
+
# edit_sekret_comment GET /comments/:id/edit(.:format)
|
|
1222
|
+
# sekret_comment GET /comments/:id(.:format)
|
|
1223
|
+
# sekret_comment PATCH/PUT /comments/:id(.:format)
|
|
1224
|
+
# sekret_comment DELETE /comments/:id(.:format)
|
|
1225
|
+
#
|
|
1226
|
+
# [:format]
|
|
1227
|
+
# Allows you to specify the default value for optional +format+
|
|
1228
|
+
# segment or disable it by supplying +false+.
|
|
1111
1229
|
#
|
|
1112
1230
|
# === Examples
|
|
1113
1231
|
#
|
|
1114
1232
|
# # routes call <tt>Admin::PostsController</tt>
|
|
1115
|
-
# resources :posts, :
|
|
1233
|
+
# resources :posts, module: "admin"
|
|
1116
1234
|
#
|
|
1117
1235
|
# # resource actions are at /admin/posts.
|
|
1118
|
-
# resources :posts, :
|
|
1236
|
+
# resources :posts, path: "admin/posts"
|
|
1119
1237
|
def resources(*resources, &block)
|
|
1120
1238
|
options = resources.extract_options!.dup
|
|
1121
1239
|
|
|
@@ -1126,6 +1244,8 @@ module ActionDispatch
|
|
|
1126
1244
|
resource_scope(:resources, Resource.new(resources.pop, options)) do
|
|
1127
1245
|
yield if block_given?
|
|
1128
1246
|
|
|
1247
|
+
concerns(options[:concerns]) if options[:concerns]
|
|
1248
|
+
|
|
1129
1249
|
collection do
|
|
1130
1250
|
get :index if parent_resource.actions.include?(:index)
|
|
1131
1251
|
post :create if parent_resource.actions.include?(:create)
|
|
@@ -1135,12 +1255,7 @@ module ActionDispatch
|
|
|
1135
1255
|
get :new
|
|
1136
1256
|
end if parent_resource.actions.include?(:new)
|
|
1137
1257
|
|
|
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
|
|
1258
|
+
set_member_mappings_for_resource
|
|
1144
1259
|
end
|
|
1145
1260
|
|
|
1146
1261
|
self
|
|
@@ -1246,10 +1361,13 @@ module ActionDispatch
|
|
|
1246
1361
|
parent_resource.instance_of?(Resource) && @scope[:shallow]
|
|
1247
1362
|
end
|
|
1248
1363
|
|
|
1364
|
+
# match 'path' => 'controller#action'
|
|
1365
|
+
# match 'path', to: 'controller#action'
|
|
1366
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
|
1249
1367
|
def match(path, *rest)
|
|
1250
1368
|
if rest.empty? && Hash === path
|
|
1251
1369
|
options = path
|
|
1252
|
-
path, to = options.find { |name,
|
|
1370
|
+
path, to = options.find { |name, _value| name.is_a?(String) }
|
|
1253
1371
|
options[:to] = to
|
|
1254
1372
|
options.delete(path)
|
|
1255
1373
|
paths = [path]
|
|
@@ -1258,22 +1376,27 @@ module ActionDispatch
|
|
|
1258
1376
|
paths = [path] + rest
|
|
1259
1377
|
end
|
|
1260
1378
|
|
|
1379
|
+
options[:anchor] = true unless options.key?(:anchor)
|
|
1380
|
+
|
|
1381
|
+
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
|
1382
|
+
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
|
1383
|
+
end
|
|
1384
|
+
|
|
1261
1385
|
if @scope[:controller] && @scope[:action]
|
|
1262
1386
|
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
|
1263
1387
|
end
|
|
1264
1388
|
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
end
|
|
1389
|
+
paths.each do |_path|
|
|
1390
|
+
route_options = options.dup
|
|
1391
|
+
route_options[:path] ||= _path if _path.is_a?(String)
|
|
1269
1392
|
|
|
1270
|
-
|
|
1393
|
+
path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
|
|
1394
|
+
if using_match_shorthand?(path_without_format, route_options)
|
|
1395
|
+
route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
|
|
1396
|
+
end
|
|
1271
1397
|
|
|
1272
|
-
|
|
1273
|
-
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
|
1398
|
+
decomposed_match(_path, route_options)
|
|
1274
1399
|
end
|
|
1275
|
-
|
|
1276
|
-
paths.each { |_path| decomposed_match(_path, options.dup) }
|
|
1277
1400
|
self
|
|
1278
1401
|
end
|
|
1279
1402
|
|
|
@@ -1312,12 +1435,20 @@ module ActionDispatch
|
|
|
1312
1435
|
options[:as] = name_for_action(options[:as], action)
|
|
1313
1436
|
end
|
|
1314
1437
|
|
|
1315
|
-
mapping = Mapping.new(@set, @scope, path, options)
|
|
1438
|
+
mapping = Mapping.new(@set, @scope, URI.parser.escape(path), options)
|
|
1316
1439
|
app, conditions, requirements, defaults, as, anchor = mapping.to_route
|
|
1317
1440
|
@set.add_route(app, conditions, requirements, defaults, as, anchor)
|
|
1318
1441
|
end
|
|
1319
1442
|
|
|
1320
|
-
def root(options={})
|
|
1443
|
+
def root(path, options={})
|
|
1444
|
+
if path.is_a?(String)
|
|
1445
|
+
options[:to] = path
|
|
1446
|
+
elsif path.is_a?(Hash) and options.empty?
|
|
1447
|
+
options = path
|
|
1448
|
+
else
|
|
1449
|
+
raise ArgumentError, "must be called with a path and/or options"
|
|
1450
|
+
end
|
|
1451
|
+
|
|
1321
1452
|
if @scope[:scope_level] == :resources
|
|
1322
1453
|
with_scope_level(:root) do
|
|
1323
1454
|
scope(parent_resource.path) do
|
|
@@ -1378,11 +1509,11 @@ module ActionDispatch
|
|
|
1378
1509
|
end
|
|
1379
1510
|
|
|
1380
1511
|
def resource_scope? #:nodoc:
|
|
1381
|
-
|
|
1512
|
+
RESOURCE_SCOPES.include? @scope[:scope_level]
|
|
1382
1513
|
end
|
|
1383
1514
|
|
|
1384
1515
|
def resource_method_scope? #:nodoc:
|
|
1385
|
-
|
|
1516
|
+
RESOURCE_METHOD_SCOPES.include? @scope[:scope_level]
|
|
1386
1517
|
end
|
|
1387
1518
|
|
|
1388
1519
|
def with_exclusive_scope
|
|
@@ -1418,18 +1549,18 @@ module ActionDispatch
|
|
|
1418
1549
|
def nested_options #:nodoc:
|
|
1419
1550
|
options = { :as => parent_resource.member_name }
|
|
1420
1551
|
options[:constraints] = {
|
|
1421
|
-
|
|
1422
|
-
} if
|
|
1552
|
+
parent_resource.nested_param => param_constraint
|
|
1553
|
+
} if param_constraint?
|
|
1423
1554
|
|
|
1424
1555
|
options
|
|
1425
1556
|
end
|
|
1426
1557
|
|
|
1427
|
-
def
|
|
1428
|
-
@scope[:constraints] && @scope[:constraints][
|
|
1558
|
+
def param_constraint? #:nodoc:
|
|
1559
|
+
@scope[:constraints] && @scope[:constraints][parent_resource.param].is_a?(Regexp)
|
|
1429
1560
|
end
|
|
1430
1561
|
|
|
1431
|
-
def
|
|
1432
|
-
@scope[:constraints][
|
|
1562
|
+
def param_constraint #:nodoc:
|
|
1563
|
+
@scope[:constraints][parent_resource.param]
|
|
1433
1564
|
end
|
|
1434
1565
|
|
|
1435
1566
|
def canonical_action?(action, flag) #:nodoc:
|
|
@@ -1442,9 +1573,9 @@ module ActionDispatch
|
|
|
1442
1573
|
|
|
1443
1574
|
def path_for_action(action, path) #:nodoc:
|
|
1444
1575
|
prefix = shallow_scoping? ?
|
|
1445
|
-
"#{@scope[:shallow_path]}/#{parent_resource.
|
|
1576
|
+
"#{@scope[:shallow_path]}/#{parent_resource.shallow_scope}" : @scope[:path]
|
|
1446
1577
|
|
|
1447
|
-
|
|
1578
|
+
if canonical_action?(action, path.blank?)
|
|
1448
1579
|
prefix.to_s
|
|
1449
1580
|
else
|
|
1450
1581
|
"#{prefix}/#{action_path(action, path)}"
|
|
@@ -1452,8 +1583,7 @@ module ActionDispatch
|
|
|
1452
1583
|
end
|
|
1453
1584
|
|
|
1454
1585
|
def action_path(name, path = nil) #:nodoc:
|
|
1455
|
-
|
|
1456
|
-
name = name.to_sym if name.is_a?(String) && !name.empty?
|
|
1586
|
+
name = name.to_sym if name.is_a?(String)
|
|
1457
1587
|
path || @scope[:path_names][name] || name.to_s
|
|
1458
1588
|
end
|
|
1459
1589
|
|
|
@@ -1503,17 +1633,136 @@ module ActionDispatch
|
|
|
1503
1633
|
end
|
|
1504
1634
|
end
|
|
1505
1635
|
end
|
|
1636
|
+
|
|
1637
|
+
def set_member_mappings_for_resource
|
|
1638
|
+
member do
|
|
1639
|
+
get :edit if parent_resource.actions.include?(:edit)
|
|
1640
|
+
get :show if parent_resource.actions.include?(:show)
|
|
1641
|
+
if parent_resource.actions.include?(:update)
|
|
1642
|
+
patch :update
|
|
1643
|
+
put :update
|
|
1644
|
+
end
|
|
1645
|
+
delete :destroy if parent_resource.actions.include?(:destroy)
|
|
1646
|
+
end
|
|
1647
|
+
end
|
|
1648
|
+
end
|
|
1649
|
+
|
|
1650
|
+
# Routing Concerns allow you to declare common routes that can be reused
|
|
1651
|
+
# inside others resources and routes.
|
|
1652
|
+
#
|
|
1653
|
+
# concern :commentable do
|
|
1654
|
+
# resources :comments
|
|
1655
|
+
# end
|
|
1656
|
+
#
|
|
1657
|
+
# concern :image_attachable do
|
|
1658
|
+
# resources :images, only: :index
|
|
1659
|
+
# end
|
|
1660
|
+
#
|
|
1661
|
+
# These concerns are used in Resources routing:
|
|
1662
|
+
#
|
|
1663
|
+
# resources :messages, concerns: [:commentable, :image_attachable]
|
|
1664
|
+
#
|
|
1665
|
+
# or in a scope or namespace:
|
|
1666
|
+
#
|
|
1667
|
+
# namespace :posts do
|
|
1668
|
+
# concerns :commentable
|
|
1669
|
+
# end
|
|
1670
|
+
module Concerns
|
|
1671
|
+
# Define a routing concern using a name.
|
|
1672
|
+
#
|
|
1673
|
+
# Concerns may be defined inline, using a block, or handled by
|
|
1674
|
+
# another object, by passing that object as the second parameter.
|
|
1675
|
+
#
|
|
1676
|
+
# The concern object, if supplied, should respond to <tt>call</tt>,
|
|
1677
|
+
# which will receive two parameters:
|
|
1678
|
+
#
|
|
1679
|
+
# * The current mapper
|
|
1680
|
+
# * A hash of options which the concern object may use
|
|
1681
|
+
#
|
|
1682
|
+
# Options may also be used by concerns defined in a block by accepting
|
|
1683
|
+
# a block parameter. So, using a block, you might do something as
|
|
1684
|
+
# simple as limit the actions available on certain resources, passing
|
|
1685
|
+
# standard resource options through the concern:
|
|
1686
|
+
#
|
|
1687
|
+
# concern :commentable do |options|
|
|
1688
|
+
# resources :comments, options
|
|
1689
|
+
# end
|
|
1690
|
+
#
|
|
1691
|
+
# resources :posts, concerns: :commentable
|
|
1692
|
+
# resources :archived_posts do
|
|
1693
|
+
# # Don't allow comments on archived posts
|
|
1694
|
+
# concerns :commentable, only: [:index, :show]
|
|
1695
|
+
# end
|
|
1696
|
+
#
|
|
1697
|
+
# Or, using a callable object, you might implement something more
|
|
1698
|
+
# specific to your application, which would be out of place in your
|
|
1699
|
+
# routes file.
|
|
1700
|
+
#
|
|
1701
|
+
# # purchasable.rb
|
|
1702
|
+
# class Purchasable
|
|
1703
|
+
# def initialize(defaults = {})
|
|
1704
|
+
# @defaults = defaults
|
|
1705
|
+
# end
|
|
1706
|
+
#
|
|
1707
|
+
# def call(mapper, options = {})
|
|
1708
|
+
# options = @defaults.merge(options)
|
|
1709
|
+
# mapper.resources :purchases
|
|
1710
|
+
# mapper.resources :receipts
|
|
1711
|
+
# mapper.resources :returns if options[:returnable]
|
|
1712
|
+
# end
|
|
1713
|
+
# end
|
|
1714
|
+
#
|
|
1715
|
+
# # routes.rb
|
|
1716
|
+
# concern :purchasable, Purchasable.new(returnable: true)
|
|
1717
|
+
#
|
|
1718
|
+
# resources :toys, concerns: :purchasable
|
|
1719
|
+
# resources :electronics, concerns: :purchasable
|
|
1720
|
+
# resources :pets do
|
|
1721
|
+
# concerns :purchasable, returnable: false
|
|
1722
|
+
# end
|
|
1723
|
+
#
|
|
1724
|
+
# Any routing helpers can be used inside a concern. If using a
|
|
1725
|
+
# callable, they're accessible from the Mapper that's passed to
|
|
1726
|
+
# <tt>call</tt>.
|
|
1727
|
+
def concern(name, callable = nil, &block)
|
|
1728
|
+
callable ||= lambda { |mapper, options| mapper.instance_exec(options, &block) }
|
|
1729
|
+
@concerns[name] = callable
|
|
1730
|
+
end
|
|
1731
|
+
|
|
1732
|
+
# Use the named concerns
|
|
1733
|
+
#
|
|
1734
|
+
# resources :posts do
|
|
1735
|
+
# concerns :commentable
|
|
1736
|
+
# end
|
|
1737
|
+
#
|
|
1738
|
+
# concerns also work in any routes helper that you want to use:
|
|
1739
|
+
#
|
|
1740
|
+
# namespace :posts do
|
|
1741
|
+
# concerns :commentable
|
|
1742
|
+
# end
|
|
1743
|
+
def concerns(*args)
|
|
1744
|
+
options = args.extract_options!
|
|
1745
|
+
args.flatten.each do |name|
|
|
1746
|
+
if concern = @concerns[name]
|
|
1747
|
+
concern.call(self, options)
|
|
1748
|
+
else
|
|
1749
|
+
raise ArgumentError, "No concern named #{name} was found!"
|
|
1750
|
+
end
|
|
1751
|
+
end
|
|
1752
|
+
end
|
|
1506
1753
|
end
|
|
1507
1754
|
|
|
1508
1755
|
def initialize(set) #:nodoc:
|
|
1509
1756
|
@set = set
|
|
1510
1757
|
@scope = { :path_names => @set.resources_path_names }
|
|
1758
|
+
@concerns = {}
|
|
1511
1759
|
end
|
|
1512
1760
|
|
|
1513
1761
|
include Base
|
|
1514
1762
|
include HttpHelpers
|
|
1515
1763
|
include Redirection
|
|
1516
1764
|
include Scoping
|
|
1765
|
+
include Concerns
|
|
1517
1766
|
include Resources
|
|
1518
1767
|
end
|
|
1519
1768
|
end
|