actionpack 4.2.11.3 → 5.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +379 -462
- data/MIT-LICENSE +1 -1
- data/README.rdoc +2 -3
- data/lib/abstract_controller.rb +0 -2
- data/lib/abstract_controller/base.rb +17 -32
- data/lib/abstract_controller/callbacks.rb +52 -19
- data/lib/abstract_controller/collector.rb +4 -9
- data/lib/abstract_controller/helpers.rb +2 -2
- data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
- data/lib/abstract_controller/rendering.rb +27 -22
- data/lib/abstract_controller/translation.rb +8 -7
- data/lib/action_controller.rb +4 -3
- data/lib/action_controller/api.rb +146 -0
- data/lib/action_controller/base.rb +6 -10
- data/lib/action_controller/caching.rb +1 -3
- data/lib/action_controller/caching/fragments.rb +48 -3
- data/lib/action_controller/form_builder.rb +48 -0
- data/lib/action_controller/log_subscriber.rb +1 -10
- data/lib/action_controller/metal.rb +89 -62
- data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
- data/lib/action_controller/metal/conditional_get.rb +65 -24
- data/lib/action_controller/metal/cookies.rb +0 -2
- data/lib/action_controller/metal/data_streaming.rb +2 -22
- data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
- data/lib/action_controller/metal/exceptions.rb +11 -6
- data/lib/action_controller/metal/force_ssl.rb +6 -6
- data/lib/action_controller/metal/head.rb +14 -7
- data/lib/action_controller/metal/helpers.rb +9 -5
- data/lib/action_controller/metal/http_authentication.rb +37 -38
- data/lib/action_controller/metal/implicit_render.rb +23 -6
- data/lib/action_controller/metal/instrumentation.rb +0 -1
- data/lib/action_controller/metal/live.rb +17 -55
- data/lib/action_controller/metal/mime_responds.rb +17 -37
- data/lib/action_controller/metal/params_wrapper.rb +8 -8
- data/lib/action_controller/metal/redirecting.rb +32 -9
- data/lib/action_controller/metal/renderers.rb +10 -8
- data/lib/action_controller/metal/rendering.rb +38 -6
- data/lib/action_controller/metal/request_forgery_protection.rb +67 -35
- data/lib/action_controller/metal/rescue.rb +2 -4
- data/lib/action_controller/metal/streaming.rb +4 -4
- data/lib/action_controller/metal/strong_parameters.rb +231 -78
- data/lib/action_controller/metal/testing.rb +1 -12
- data/lib/action_controller/metal/url_for.rb +12 -5
- data/lib/action_controller/renderer.rb +111 -0
- data/lib/action_controller/template_assertions.rb +9 -0
- data/lib/action_controller/test_case.rb +267 -363
- data/lib/action_dispatch.rb +2 -1
- data/lib/action_dispatch/http/cache.rb +23 -26
- data/lib/action_dispatch/http/filter_parameters.rb +6 -8
- data/lib/action_dispatch/http/filter_redirect.rb +7 -8
- data/lib/action_dispatch/http/headers.rb +28 -11
- data/lib/action_dispatch/http/mime_negotiation.rb +40 -26
- data/lib/action_dispatch/http/mime_type.rb +92 -61
- data/lib/action_dispatch/http/mime_types.rb +1 -4
- data/lib/action_dispatch/http/parameter_filter.rb +18 -8
- data/lib/action_dispatch/http/parameters.rb +45 -41
- data/lib/action_dispatch/http/request.rb +146 -82
- data/lib/action_dispatch/http/response.rb +180 -99
- data/lib/action_dispatch/http/url.rb +117 -8
- data/lib/action_dispatch/journey/formatter.rb +34 -28
- data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
- data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
- data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
- data/lib/action_dispatch/journey/nodes/node.rb +14 -4
- data/lib/action_dispatch/journey/parser_extras.rb +4 -0
- data/lib/action_dispatch/journey/path/pattern.rb +37 -41
- data/lib/action_dispatch/journey/route.rb +71 -17
- data/lib/action_dispatch/journey/router.rb +5 -6
- data/lib/action_dispatch/journey/router/utils.rb +5 -5
- data/lib/action_dispatch/journey/routes.rb +14 -15
- data/lib/action_dispatch/journey/visitors.rb +86 -43
- data/lib/action_dispatch/middleware/cookies.rb +184 -135
- data/lib/action_dispatch/middleware/debug_exceptions.rb +115 -45
- data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -20
- data/lib/action_dispatch/middleware/flash.rb +61 -45
- data/lib/action_dispatch/middleware/load_interlock.rb +21 -0
- data/lib/action_dispatch/middleware/params_parser.rb +30 -46
- data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
- data/lib/action_dispatch/middleware/reloader.rb +2 -4
- data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
- data/lib/action_dispatch/middleware/request_id.rb +11 -6
- data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
- data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
- data/lib/action_dispatch/middleware/session/cookie_store.rb +29 -23
- data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
- data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
- data/lib/action_dispatch/middleware/ssl.rb +93 -36
- data/lib/action_dispatch/middleware/stack.rb +43 -48
- data/lib/action_dispatch/middleware/static.rb +52 -40
- data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
- data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
- data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
- data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
- data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
- data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
- data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
- data/lib/action_dispatch/railtie.rb +0 -2
- data/lib/action_dispatch/request/session.rb +66 -34
- data/lib/action_dispatch/request/utils.rb +51 -19
- data/lib/action_dispatch/routing.rb +3 -8
- data/lib/action_dispatch/routing/inspector.rb +6 -30
- data/lib/action_dispatch/routing/mapper.rb +447 -322
- data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
- data/lib/action_dispatch/routing/redirection.rb +3 -3
- data/lib/action_dispatch/routing/route_set.rb +124 -227
- data/lib/action_dispatch/routing/url_for.rb +27 -10
- data/lib/action_dispatch/testing/assertions.rb +1 -1
- data/lib/action_dispatch/testing/assertions/response.rb +27 -9
- data/lib/action_dispatch/testing/assertions/routing.rb +9 -9
- data/lib/action_dispatch/testing/integration.rb +237 -76
- data/lib/action_dispatch/testing/test_process.rb +5 -5
- data/lib/action_dispatch/testing/test_request.rb +12 -21
- data/lib/action_dispatch/testing/test_response.rb +1 -4
- data/lib/action_pack.rb +1 -1
- data/lib/action_pack/gem_version.rb +4 -4
- metadata +26 -25
- data/lib/action_controller/metal/hide_actions.rb +0 -40
- data/lib/action_controller/metal/rack_delegation.rb +0 -32
- data/lib/action_controller/middleware.rb +0 -39
- data/lib/action_controller/model_naming.rb +0 -12
- data/lib/action_dispatch/journey/router/strexp.rb +0 -27
- data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
- data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
- data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
@@ -1,32 +1,64 @@
|
|
1
1
|
module ActionDispatch
|
2
|
-
class Request
|
2
|
+
class Request
|
3
3
|
class Utils # :nodoc:
|
4
4
|
|
5
5
|
mattr_accessor :perform_deep_munge
|
6
6
|
self.perform_deep_munge = true
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
def self.normalize_encode_params(params)
|
9
|
+
if perform_deep_munge
|
10
|
+
NoNilParamEncoder.normalize_encode_params params
|
11
|
+
else
|
12
|
+
ParamEncoder.normalize_encode_params params
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.check_param_encoding(params)
|
17
|
+
case params
|
18
|
+
when Array
|
19
|
+
params.each { |element| check_param_encoding(element) }
|
20
|
+
when Hash
|
21
|
+
params.each_value { |value| check_param_encoding(value) }
|
22
|
+
when String
|
23
|
+
unless params.valid_encoding?
|
24
|
+
# Raise Rack::Utils::InvalidParameterError for consistency with Rack.
|
25
|
+
# ActionDispatch::Request#GET will re-raise as a BadRequest error.
|
26
|
+
raise Rack::Utils::InvalidParameterError, "Non UTF-8 value: #{params}"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
12
30
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
31
|
+
class ParamEncoder # :nodoc:
|
32
|
+
# Convert nested Hash to HashWithIndifferentAccess.
|
33
|
+
#
|
34
|
+
def self.normalize_encode_params(params)
|
35
|
+
case params
|
36
|
+
when Array
|
37
|
+
handle_array params
|
38
|
+
when Hash
|
39
|
+
if params.has_key?(:tempfile)
|
40
|
+
ActionDispatch::Http::UploadedFile.new(params)
|
41
|
+
else
|
42
|
+
params.each_with_object({}) do |(key, val), new_hash|
|
43
|
+
new_hash[key] = normalize_encode_params(val)
|
44
|
+
end.with_indifferent_access
|
25
45
|
end
|
26
|
-
|
46
|
+
else
|
47
|
+
params
|
27
48
|
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.handle_array(params)
|
52
|
+
params.map! { |el| normalize_encode_params(el) }
|
53
|
+
end
|
54
|
+
end
|
28
55
|
|
29
|
-
|
56
|
+
# Remove nils from the params hash
|
57
|
+
class NoNilParamEncoder < ParamEncoder # :nodoc:
|
58
|
+
def self.handle_array(params)
|
59
|
+
list = super
|
60
|
+
list.compact!
|
61
|
+
list
|
30
62
|
end
|
31
63
|
end
|
32
64
|
end
|
@@ -1,8 +1,3 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
require 'active_support/core_ext/object/to_param'
|
3
|
-
require 'active_support/core_ext/regexp'
|
4
|
-
require 'active_support/dependencies/autoload'
|
5
|
-
|
6
1
|
module ActionDispatch
|
7
2
|
# The routing module provides URL rewriting in native Ruby. It's a way to
|
8
3
|
# redirect incoming requests to controllers and actions. This replaces
|
@@ -58,7 +53,7 @@ module ActionDispatch
|
|
58
53
|
# resources :posts, :comments
|
59
54
|
# end
|
60
55
|
#
|
61
|
-
#
|
56
|
+
# Alternatively, you can add prefixes to your path without using a separate
|
62
57
|
# directory by using +scope+. +scope+ takes additional options which
|
63
58
|
# apply to all enclosed routes.
|
64
59
|
#
|
@@ -151,6 +146,7 @@ module ActionDispatch
|
|
151
146
|
# get 'geocode/:postalcode' => :show, constraints: {
|
152
147
|
# postalcode: /\d{5}(-\d{4})?/
|
153
148
|
# }
|
149
|
+
# end
|
154
150
|
#
|
155
151
|
# Constraints can include the 'ignorecase' and 'extended syntax' regular
|
156
152
|
# expression modifiers:
|
@@ -232,7 +228,6 @@ module ActionDispatch
|
|
232
228
|
# def send_to_jail
|
233
229
|
# get '/jail'
|
234
230
|
# assert_response :success
|
235
|
-
# assert_template "jail/front"
|
236
231
|
# end
|
237
232
|
#
|
238
233
|
# def goes_to_login
|
@@ -242,7 +237,7 @@ module ActionDispatch
|
|
242
237
|
#
|
243
238
|
# == View a list of all your routes
|
244
239
|
#
|
245
|
-
#
|
240
|
+
# rails routes
|
246
241
|
#
|
247
242
|
# Target specific controllers by prefixing the command with <tt>CONTROLLER=x</tt>.
|
248
243
|
#
|
@@ -16,10 +16,6 @@ module ActionDispatch
|
|
16
16
|
app.app
|
17
17
|
end
|
18
18
|
|
19
|
-
def verb
|
20
|
-
super.source.gsub(/[$^]/, '')
|
21
|
-
end
|
22
|
-
|
23
19
|
def path
|
24
20
|
super.spec.to_s
|
25
21
|
end
|
@@ -28,23 +24,6 @@ module ActionDispatch
|
|
28
24
|
super.to_s
|
29
25
|
end
|
30
26
|
|
31
|
-
def regexp
|
32
|
-
__getobj__.path.to_regexp
|
33
|
-
end
|
34
|
-
|
35
|
-
def json_regexp
|
36
|
-
str = regexp.inspect.
|
37
|
-
sub('\\A' , '^').
|
38
|
-
sub('\\Z' , '$').
|
39
|
-
sub('\\z' , '$').
|
40
|
-
sub(/^\// , '').
|
41
|
-
sub(/\/[a-z]*$/ , '').
|
42
|
-
gsub(/\(\?#.+\)/ , '').
|
43
|
-
gsub(/\(\?-\w+:/ , '(').
|
44
|
-
gsub(/\s/ , '')
|
45
|
-
Regexp.new(str).source
|
46
|
-
end
|
47
|
-
|
48
27
|
def reqs
|
49
28
|
@reqs ||= begin
|
50
29
|
reqs = endpoint
|
@@ -62,7 +41,7 @@ module ActionDispatch
|
|
62
41
|
end
|
63
42
|
|
64
43
|
def internal?
|
65
|
-
controller.to_s =~ %r{\Arails/(info|mailers|welcome)}
|
44
|
+
controller.to_s =~ %r{\Arails/(info|mailers|welcome)}
|
66
45
|
end
|
67
46
|
|
68
47
|
def engine?
|
@@ -114,16 +93,13 @@ module ActionDispatch
|
|
114
93
|
def collect_routes(routes)
|
115
94
|
routes.collect do |route|
|
116
95
|
RouteWrapper.new(route)
|
117
|
-
end.reject do |route|
|
118
|
-
route.internal?
|
119
|
-
end.collect do |route|
|
96
|
+
end.reject(&:internal?).collect do |route|
|
120
97
|
collect_engine_routes(route)
|
121
98
|
|
122
|
-
{ name:
|
123
|
-
verb:
|
124
|
-
path:
|
125
|
-
reqs:
|
126
|
-
regexp: route.json_regexp }
|
99
|
+
{ name: route.name,
|
100
|
+
verb: route.verb,
|
101
|
+
path: route.path,
|
102
|
+
reqs: route.reqs }
|
127
103
|
end
|
128
104
|
end
|
129
105
|
|
@@ -1,24 +1,23 @@
|
|
1
|
-
require 'active_support/core_ext/hash/except'
|
2
1
|
require 'active_support/core_ext/hash/reverse_merge'
|
3
2
|
require 'active_support/core_ext/hash/slice'
|
4
3
|
require 'active_support/core_ext/enumerable'
|
5
4
|
require 'active_support/core_ext/array/extract_options'
|
6
|
-
require 'active_support/core_ext/
|
7
|
-
require 'active_support/core_ext/string/filters'
|
8
|
-
require 'active_support/inflector'
|
5
|
+
require 'active_support/core_ext/regexp'
|
9
6
|
require 'action_dispatch/routing/redirection'
|
10
7
|
require 'action_dispatch/routing/endpoint'
|
11
|
-
require 'active_support/deprecation'
|
12
8
|
|
13
9
|
module ActionDispatch
|
14
10
|
module Routing
|
15
11
|
class Mapper
|
16
12
|
URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
|
17
13
|
|
18
|
-
class Constraints < Endpoint #:nodoc:
|
14
|
+
class Constraints < Routing::Endpoint #:nodoc:
|
19
15
|
attr_reader :app, :constraints
|
20
16
|
|
21
|
-
|
17
|
+
SERVE = ->(app, req) { app.serve req }
|
18
|
+
CALL = ->(app, req) { app.call req.env }
|
19
|
+
|
20
|
+
def initialize(app, constraints, strategy)
|
22
21
|
# Unwrap Constraints objects. I don't actually think it's possible
|
23
22
|
# to pass a Constraints object to this constructor, but there were
|
24
23
|
# multiple places that kept testing children of this object. I
|
@@ -28,12 +27,12 @@ module ActionDispatch
|
|
28
27
|
app = app.app
|
29
28
|
end
|
30
29
|
|
31
|
-
@
|
30
|
+
@strategy = strategy
|
32
31
|
|
33
32
|
@app, @constraints, = app, constraints
|
34
33
|
end
|
35
34
|
|
36
|
-
def dispatcher?; @
|
35
|
+
def dispatcher?; @strategy == SERVE; end
|
37
36
|
|
38
37
|
def matches?(req)
|
39
38
|
@constraints.all? do |constraint|
|
@@ -45,11 +44,7 @@ module ActionDispatch
|
|
45
44
|
def serve(req)
|
46
45
|
return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
|
47
46
|
|
48
|
-
|
49
|
-
@app.serve req
|
50
|
-
else
|
51
|
-
@app.call req.env
|
52
|
-
end
|
47
|
+
@strategy.call @app, req
|
53
48
|
end
|
54
49
|
|
55
50
|
private
|
@@ -60,103 +55,169 @@ module ActionDispatch
|
|
60
55
|
|
61
56
|
class Mapping #:nodoc:
|
62
57
|
ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
|
63
|
-
OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
|
64
58
|
|
65
|
-
attr_reader :requirements, :
|
66
|
-
attr_reader :to, :default_controller, :default_action
|
59
|
+
attr_reader :requirements, :defaults
|
60
|
+
attr_reader :to, :default_controller, :default_action
|
61
|
+
attr_reader :required_defaults, :ast
|
67
62
|
|
68
|
-
def self.build(scope, set,
|
63
|
+
def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
69
64
|
options = scope[:options].merge(options) if scope[:options]
|
70
65
|
|
71
|
-
|
72
|
-
|
73
|
-
options.delete :shallow_path
|
74
|
-
options.delete :shallow_prefix
|
75
|
-
options.delete :shallow
|
66
|
+
defaults = (scope[:defaults] || {}).dup
|
67
|
+
scope_constraints = scope[:constraints] || {}
|
76
68
|
|
77
|
-
defaults
|
69
|
+
new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
|
70
|
+
end
|
78
71
|
|
79
|
-
|
72
|
+
def self.check_via(via)
|
73
|
+
if via.empty?
|
74
|
+
msg = "You should not use the `match` method in your router without specifying an HTTP method.\n" \
|
75
|
+
"If you want to expose your action to both GET and POST, add `via: [:get, :post]` option.\n" \
|
76
|
+
"If you want to expose your action to GET, use `get` in the router:\n" \
|
77
|
+
" Instead of: match \"controller#action\"\n" \
|
78
|
+
" Do: get \"controller#action\""
|
79
|
+
raise ArgumentError, msg
|
80
|
+
end
|
81
|
+
via
|
80
82
|
end
|
81
83
|
|
82
|
-
def
|
83
|
-
|
84
|
-
|
85
|
-
|
84
|
+
def self.normalize_path(path, format)
|
85
|
+
path = Mapper.normalize_path(path)
|
86
|
+
|
87
|
+
if format == true
|
88
|
+
"#{path}.:format"
|
89
|
+
elsif optional_format?(path, format)
|
90
|
+
"#{path}(.:format)"
|
91
|
+
else
|
92
|
+
path
|
93
|
+
end
|
94
|
+
end
|
86
95
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
@as = as
|
91
|
-
@anchor = options.delete :anchor
|
96
|
+
def self.optional_format?(path, format)
|
97
|
+
format != false && !path.include?(':format') && !path.end_with?('/')
|
98
|
+
end
|
92
99
|
|
93
|
-
|
94
|
-
|
95
|
-
|
100
|
+
def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
|
101
|
+
@defaults = defaults
|
102
|
+
@set = set
|
96
103
|
|
97
|
-
|
98
|
-
|
99
|
-
|
104
|
+
@to = to
|
105
|
+
@default_controller = controller
|
106
|
+
@default_action = default_action
|
107
|
+
@ast = ast
|
108
|
+
@anchor = anchor
|
109
|
+
@via = via
|
100
110
|
|
101
|
-
|
111
|
+
path_params = ast.find_all(&:symbol?).map(&:to_sym)
|
102
112
|
|
113
|
+
options = add_wildcard_options(options, formatted, ast)
|
103
114
|
|
104
|
-
|
105
|
-
constraints = constraints(options, path_params)
|
115
|
+
options = normalize_options!(options, path_params, modyoule)
|
106
116
|
|
107
|
-
|
117
|
+
split_options = constraints(options, path_params)
|
108
118
|
|
109
|
-
|
119
|
+
constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
|
110
120
|
|
111
121
|
if options_constraints.is_a?(Hash)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
122
|
+
@defaults = Hash[options_constraints.find_all { |key, default|
|
123
|
+
URL_OPTIONS.include?(key) && (String === default || Fixnum === default)
|
124
|
+
}].merge @defaults
|
125
|
+
@blocks = blocks
|
126
|
+
constraints.merge! options_constraints
|
127
|
+
else
|
128
|
+
@blocks = blocks(options_constraints)
|
118
129
|
end
|
119
130
|
|
120
|
-
|
131
|
+
requirements, conditions = split_constraints path_params, constraints
|
132
|
+
verify_regexp_requirements requirements.map(&:last).grep(Regexp)
|
121
133
|
|
122
|
-
|
123
|
-
@conditions[:parsed_path_info] = ast
|
134
|
+
formats = normalize_format(formatted)
|
124
135
|
|
125
|
-
|
126
|
-
|
136
|
+
@requirements = formats[:requirements].merge Hash[requirements]
|
137
|
+
@conditions = Hash[conditions]
|
138
|
+
@defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
|
139
|
+
|
140
|
+
@required_defaults = (split_options[:required_defaults] || []).map(&:first)
|
127
141
|
end
|
128
142
|
|
129
|
-
def
|
130
|
-
|
143
|
+
def make_route(name, precedence)
|
144
|
+
route = Journey::Route.new(name,
|
145
|
+
application,
|
146
|
+
path,
|
147
|
+
conditions,
|
148
|
+
required_defaults,
|
149
|
+
defaults,
|
150
|
+
request_method,
|
151
|
+
precedence)
|
152
|
+
|
153
|
+
route
|
131
154
|
end
|
132
155
|
|
133
|
-
|
156
|
+
def application
|
157
|
+
app(@blocks)
|
158
|
+
end
|
134
159
|
|
135
|
-
|
136
|
-
|
160
|
+
def path
|
161
|
+
build_path @ast, requirements, @anchor
|
162
|
+
end
|
137
163
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
"#{path}(.:format)"
|
142
|
-
else
|
143
|
-
path
|
144
|
-
end
|
145
|
-
end
|
164
|
+
def conditions
|
165
|
+
build_conditions @conditions, @set.request_class
|
166
|
+
end
|
146
167
|
|
147
|
-
|
148
|
-
|
168
|
+
def build_conditions(current_conditions, request_class)
|
169
|
+
conditions = current_conditions.dup
|
170
|
+
|
171
|
+
conditions.keep_if do |k, _|
|
172
|
+
request_class.public_method_defined?(k)
|
149
173
|
end
|
174
|
+
end
|
175
|
+
private :build_conditions
|
176
|
+
|
177
|
+
def request_method
|
178
|
+
@via.map { |x| Journey::Route.verb_matcher(x) }
|
179
|
+
end
|
180
|
+
private :request_method
|
181
|
+
|
182
|
+
JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
|
183
|
+
|
184
|
+
def build_path(ast, requirements, anchor)
|
185
|
+
pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
|
186
|
+
|
187
|
+
# Get all the symbol nodes followed by literals that are not the
|
188
|
+
# dummy node.
|
189
|
+
symbols = ast.find_all { |n|
|
190
|
+
n.cat? && n.left.symbol? && n.right.cat? && n.right.left.literal?
|
191
|
+
}.map(&:left)
|
192
|
+
|
193
|
+
# Get all the symbol nodes preceded by literals.
|
194
|
+
symbols.concat ast.find_all { |n|
|
195
|
+
n.cat? && n.left.literal? && n.right.cat? && n.right.left.symbol?
|
196
|
+
}.map { |n| n.right.left }
|
150
197
|
|
151
|
-
|
198
|
+
symbols.each { |x|
|
199
|
+
x.regexp = /(?:#{Regexp.union(x.regexp, '-')})+/
|
200
|
+
}
|
201
|
+
|
202
|
+
pattern
|
203
|
+
end
|
204
|
+
private :build_path
|
205
|
+
|
206
|
+
|
207
|
+
private
|
208
|
+
def add_wildcard_options(options, formatted, path_ast)
|
152
209
|
# Add a constraint for wildcard route to make it non-greedy and match the
|
153
210
|
# optional format part of the route by default
|
154
211
|
if formatted != false
|
155
|
-
path_ast.grep(Journey::Nodes::Star)
|
156
|
-
|
157
|
-
|
212
|
+
path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
|
213
|
+
hash[node.name.to_sym] ||= /.+?/
|
214
|
+
}.merge options
|
215
|
+
else
|
216
|
+
options
|
158
217
|
end
|
218
|
+
end
|
159
219
|
|
220
|
+
def normalize_options!(options, path_params, modyoule)
|
160
221
|
if path_params.include?(:controller)
|
161
222
|
raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
|
162
223
|
|
@@ -181,74 +242,54 @@ module ActionDispatch
|
|
181
242
|
end
|
182
243
|
|
183
244
|
def split_constraints(path_params, constraints)
|
184
|
-
constraints.
|
185
|
-
|
186
|
-
verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
|
187
|
-
@requirements[key] = requirement
|
188
|
-
else
|
189
|
-
@conditions[key] = requirement
|
190
|
-
end
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
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
|
245
|
+
constraints.partition do |key, requirement|
|
246
|
+
path_params.include?(key) || key == :controller
|
203
247
|
end
|
204
248
|
end
|
205
249
|
|
206
|
-
def
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
250
|
+
def normalize_format(formatted)
|
251
|
+
case formatted
|
252
|
+
when true
|
253
|
+
{ requirements: { format: /.+/ },
|
254
|
+
defaults: {} }
|
255
|
+
when Regexp
|
256
|
+
{ requirements: { format: formatted },
|
257
|
+
defaults: { format: nil } }
|
258
|
+
when String
|
259
|
+
{ requirements: { format: Regexp.compile(formatted) },
|
260
|
+
defaults: { format: formatted } }
|
261
|
+
else
|
262
|
+
{ requirements: { }, defaults: { } }
|
213
263
|
end
|
214
264
|
end
|
215
265
|
|
216
|
-
def
|
217
|
-
|
218
|
-
|
219
|
-
|
266
|
+
def verify_regexp_requirements(requirements)
|
267
|
+
requirements.each do |requirement|
|
268
|
+
if requirement.source =~ ANCHOR_CHARACTERS_REGEX
|
269
|
+
raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
|
220
270
|
end
|
221
|
-
end
|
222
|
-
end
|
223
271
|
|
224
|
-
|
225
|
-
|
226
|
-
|
272
|
+
if requirement.multiline?
|
273
|
+
raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
|
274
|
+
end
|
227
275
|
end
|
228
276
|
end
|
229
277
|
|
230
|
-
def
|
231
|
-
|
232
|
-
|
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
|
241
|
-
|
242
|
-
conditions[:request_method] = via.map { |m| m.to_s.dasherize.upcase }
|
278
|
+
def normalize_defaults(options)
|
279
|
+
Hash[options.reject { |_, default| Regexp === default }]
|
243
280
|
end
|
244
281
|
|
245
282
|
def app(blocks)
|
246
|
-
if to.
|
247
|
-
|
248
|
-
elsif blocks.any?
|
249
|
-
Constraints.new(dispatcher(defaults), blocks, true)
|
283
|
+
if to.is_a?(Class) && to < ActionController::Metal
|
284
|
+
Routing::RouteSet::StaticDispatcher.new to
|
250
285
|
else
|
251
|
-
|
286
|
+
if to.respond_to?(:call)
|
287
|
+
Constraints.new(to, blocks, Constraints::CALL)
|
288
|
+
elsif blocks.any?
|
289
|
+
Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
|
290
|
+
else
|
291
|
+
dispatcher(defaults.key?(:controller))
|
292
|
+
end
|
252
293
|
end
|
253
294
|
end
|
254
295
|
|
@@ -280,22 +321,8 @@ module ActionDispatch
|
|
280
321
|
end
|
281
322
|
|
282
323
|
def split_to(to)
|
283
|
-
|
284
|
-
|
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]
|
324
|
+
if to =~ /#/
|
325
|
+
to.split('#')
|
299
326
|
else
|
300
327
|
[]
|
301
328
|
end
|
@@ -320,40 +347,29 @@ module ActionDispatch
|
|
320
347
|
yield
|
321
348
|
end
|
322
349
|
|
323
|
-
def blocks(
|
324
|
-
|
325
|
-
|
326
|
-
[options_constraints]
|
327
|
-
else
|
328
|
-
scope_blocks || []
|
350
|
+
def blocks(callable_constraint)
|
351
|
+
unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
|
352
|
+
raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
|
329
353
|
end
|
354
|
+
[callable_constraint]
|
330
355
|
end
|
331
356
|
|
332
357
|
def constraints(options, path_params)
|
333
|
-
|
334
|
-
required_defaults = []
|
335
|
-
options.each_pair do |key, option|
|
358
|
+
options.group_by do |key, option|
|
336
359
|
if Regexp === option
|
337
|
-
constraints
|
360
|
+
:constraints
|
338
361
|
else
|
339
|
-
|
362
|
+
if path_params.include?(key)
|
363
|
+
:path_params
|
364
|
+
else
|
365
|
+
:required_defaults
|
366
|
+
end
|
340
367
|
end
|
341
368
|
end
|
342
|
-
@conditions[:required_defaults] = required_defaults
|
343
|
-
constraints
|
344
369
|
end
|
345
370
|
|
346
|
-
def
|
347
|
-
|
348
|
-
end
|
349
|
-
|
350
|
-
def path_ast(path)
|
351
|
-
parser = Journey::Parser.new
|
352
|
-
parser.parse path
|
353
|
-
end
|
354
|
-
|
355
|
-
def dispatcher(defaults)
|
356
|
-
@set.dispatcher defaults
|
371
|
+
def dispatcher(raise_on_name_error)
|
372
|
+
Routing::RouteSet::Dispatcher.new raise_on_name_error
|
357
373
|
end
|
358
374
|
end
|
359
375
|
|
@@ -385,7 +401,8 @@ module ActionDispatch
|
|
385
401
|
# because this means it will be matched first. As this is the most popular route
|
386
402
|
# of most Rails applications, this is beneficial.
|
387
403
|
def root(options = {})
|
388
|
-
|
404
|
+
name = has_named_route?(:root) ? nil : :root
|
405
|
+
match '/', { as: name, via: :get }.merge!(options)
|
389
406
|
end
|
390
407
|
|
391
408
|
# Matches a url pattern to one or more routes.
|
@@ -435,7 +452,7 @@ module ActionDispatch
|
|
435
452
|
# A pattern can also point to a +Rack+ endpoint i.e. anything that
|
436
453
|
# responds to +call+:
|
437
454
|
#
|
438
|
-
# match 'photos/:id', to:
|
455
|
+
# match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
|
439
456
|
# match 'photos/:id', to: PhotoRackApp, via: :get
|
440
457
|
# # Yes, controller actions are just rack endpoints
|
441
458
|
# match 'photos/:id', to: PhotosController.action(:show), via: :get
|
@@ -460,6 +477,21 @@ module ActionDispatch
|
|
460
477
|
# dynamic segment used to generate the routes).
|
461
478
|
# You can access that segment from your controller using
|
462
479
|
# <tt>params[<:param>]</tt>.
|
480
|
+
# In your router:
|
481
|
+
#
|
482
|
+
# resources :user, param: :name
|
483
|
+
#
|
484
|
+
# You can override <tt>ActiveRecord::Base#to_param</tt> of a related
|
485
|
+
# model to construct a URL:
|
486
|
+
#
|
487
|
+
# class User < ActiveRecord::Base
|
488
|
+
# def to_param
|
489
|
+
# name
|
490
|
+
# end
|
491
|
+
# end
|
492
|
+
#
|
493
|
+
# user = User.find_by(name: 'Phusion')
|
494
|
+
# user_path(user) # => "/users/Phusion"
|
463
495
|
#
|
464
496
|
# [:path]
|
465
497
|
# The path prefix for the routes.
|
@@ -487,7 +519,7 @@ module ActionDispatch
|
|
487
519
|
# +call+ or a string representing a controller's action.
|
488
520
|
#
|
489
521
|
# match 'path', to: 'controller#action', via: :get
|
490
|
-
# match 'path', to:
|
522
|
+
# match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
|
491
523
|
# match 'path', to: RackApp, via: :get
|
492
524
|
#
|
493
525
|
# [:on]
|
@@ -568,17 +600,20 @@ module ActionDispatch
|
|
568
600
|
def mount(app, options = nil)
|
569
601
|
if options
|
570
602
|
path = options.delete(:at)
|
571
|
-
|
572
|
-
unless Hash === app
|
573
|
-
raise ArgumentError, "must be called with mount point"
|
574
|
-
end
|
575
|
-
|
603
|
+
elsif Hash === app
|
576
604
|
options = app
|
577
605
|
app, path = options.find { |k, _| k.respond_to?(:call) }
|
578
606
|
options.delete(app) if app
|
579
607
|
end
|
580
608
|
|
581
|
-
raise "A rack application must be specified" unless
|
609
|
+
raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
|
610
|
+
raise ArgumentError, <<-MSG.strip_heredoc unless path
|
611
|
+
Must be called with mount point
|
612
|
+
|
613
|
+
mount SomeRackApp, at: "some_route"
|
614
|
+
or
|
615
|
+
mount(SomeRackApp => "some_route")
|
616
|
+
MSG
|
582
617
|
|
583
618
|
rails_app = rails_app? app
|
584
619
|
options[:as] ||= app_name(app, rails_app)
|
@@ -605,7 +640,7 @@ module ActionDispatch
|
|
605
640
|
|
606
641
|
# Query if the following named route was already defined.
|
607
642
|
def has_named_route?(name)
|
608
|
-
@set.named_routes.
|
643
|
+
@set.named_routes.key? name
|
609
644
|
end
|
610
645
|
|
611
646
|
private
|
@@ -688,7 +723,11 @@ module ActionDispatch
|
|
688
723
|
def map_method(method, args, &block)
|
689
724
|
options = args.extract_options!
|
690
725
|
options[:via] = method
|
691
|
-
|
726
|
+
if options.key?(:defaults)
|
727
|
+
defaults(options.delete(:defaults)) { match(*args, options, &block) }
|
728
|
+
else
|
729
|
+
match(*args, options, &block)
|
730
|
+
end
|
692
731
|
self
|
693
732
|
end
|
694
733
|
end
|
@@ -792,7 +831,7 @@ module ActionDispatch
|
|
792
831
|
|
793
832
|
if options[:constraints].is_a?(Hash)
|
794
833
|
defaults = options[:constraints].select do |k, v|
|
795
|
-
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(
|
834
|
+
URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Fixnum))
|
796
835
|
end
|
797
836
|
|
798
837
|
(options[:defaults] ||= {}).reverse_merge!(defaults)
|
@@ -800,16 +839,25 @@ module ActionDispatch
|
|
800
839
|
block, options[:constraints] = options[:constraints], {}
|
801
840
|
end
|
802
841
|
|
842
|
+
if options.key?(:only) || options.key?(:except)
|
843
|
+
scope[:action_options] = { only: options.delete(:only),
|
844
|
+
except: options.delete(:except) }
|
845
|
+
end
|
846
|
+
|
847
|
+
if options.key? :anchor
|
848
|
+
raise ArgumentError, 'anchor is ignored unless passed to `match`'
|
849
|
+
end
|
850
|
+
|
803
851
|
@scope.options.each do |option|
|
804
852
|
if option == :blocks
|
805
853
|
value = block
|
806
854
|
elsif option == :options
|
807
855
|
value = options
|
808
856
|
else
|
809
|
-
value = options.delete(option)
|
857
|
+
value = options.delete(option) { POISON }
|
810
858
|
end
|
811
859
|
|
812
|
-
|
860
|
+
unless POISON == value
|
813
861
|
scope[option] = send("merge_#{option}_scope", @scope[option], value)
|
814
862
|
end
|
815
863
|
end
|
@@ -821,14 +869,18 @@ module ActionDispatch
|
|
821
869
|
@scope = @scope.parent
|
822
870
|
end
|
823
871
|
|
872
|
+
POISON = Object.new # :nodoc:
|
873
|
+
|
824
874
|
# Scopes routes to a specific controller
|
825
875
|
#
|
826
876
|
# controller "food" do
|
827
|
-
# match "bacon", action:
|
877
|
+
# match "bacon", action: :bacon, via: :get
|
828
878
|
# end
|
829
|
-
def controller(controller
|
830
|
-
|
831
|
-
|
879
|
+
def controller(controller)
|
880
|
+
@scope = @scope.new(controller: controller)
|
881
|
+
yield
|
882
|
+
ensure
|
883
|
+
@scope = @scope.parent
|
832
884
|
end
|
833
885
|
|
834
886
|
# Scopes routes to a specific namespace. For example:
|
@@ -874,13 +926,14 @@ module ActionDispatch
|
|
874
926
|
|
875
927
|
defaults = {
|
876
928
|
module: path,
|
877
|
-
path: options.fetch(:path, path),
|
878
929
|
as: options.fetch(:as, path),
|
879
930
|
shallow_path: options.fetch(:path, path),
|
880
931
|
shallow_prefix: options.fetch(:as, path)
|
881
932
|
}
|
882
933
|
|
883
|
-
|
934
|
+
path_scope(options.delete(:path) { path }) do
|
935
|
+
scope(defaults.merge!(options)) { yield }
|
936
|
+
end
|
884
937
|
end
|
885
938
|
|
886
939
|
# === Parameter Restriction
|
@@ -917,7 +970,7 @@ module ActionDispatch
|
|
917
970
|
#
|
918
971
|
# Requests to routes can be constrained based on specific criteria:
|
919
972
|
#
|
920
|
-
# constraints(
|
973
|
+
# constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
|
921
974
|
# resources :iphones
|
922
975
|
# end
|
923
976
|
#
|
@@ -948,7 +1001,10 @@ module ActionDispatch
|
|
948
1001
|
# end
|
949
1002
|
# Using this, the +:id+ parameter here will default to 'home'.
|
950
1003
|
def defaults(defaults = {})
|
951
|
-
scope(:defaults
|
1004
|
+
@scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
|
1005
|
+
yield
|
1006
|
+
ensure
|
1007
|
+
@scope = @scope.parent
|
952
1008
|
end
|
953
1009
|
|
954
1010
|
private
|
@@ -980,6 +1036,14 @@ module ActionDispatch
|
|
980
1036
|
child
|
981
1037
|
end
|
982
1038
|
|
1039
|
+
def merge_via_scope(parent, child) #:nodoc:
|
1040
|
+
child
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def merge_format_scope(parent, child) #:nodoc:
|
1044
|
+
child
|
1045
|
+
end
|
1046
|
+
|
983
1047
|
def merge_path_names_scope(parent, child) #:nodoc:
|
984
1048
|
merge_options_scope(parent, child)
|
985
1049
|
end
|
@@ -999,16 +1063,12 @@ module ActionDispatch
|
|
999
1063
|
end
|
1000
1064
|
|
1001
1065
|
def merge_options_scope(parent, child) #:nodoc:
|
1002
|
-
(parent || {}).
|
1066
|
+
(parent || {}).merge(child)
|
1003
1067
|
end
|
1004
1068
|
|
1005
1069
|
def merge_shallow_scope(parent, child) #:nodoc:
|
1006
1070
|
child ? true : false
|
1007
1071
|
end
|
1008
|
-
|
1009
|
-
def override_keys(child) #:nodoc:
|
1010
|
-
child.key?(:only) || child.key?(:except) ? [:only, :except] : []
|
1011
|
-
end
|
1012
1072
|
end
|
1013
1073
|
|
1014
1074
|
# Resource routing allows you to quickly declare all of the common routes
|
@@ -1058,27 +1118,34 @@ module ActionDispatch
|
|
1058
1118
|
CANONICAL_ACTIONS = %w(index create new show update destroy)
|
1059
1119
|
|
1060
1120
|
class Resource #:nodoc:
|
1061
|
-
attr_reader :controller, :path, :
|
1121
|
+
attr_reader :controller, :path, :param
|
1062
1122
|
|
1063
|
-
def initialize(entities, options = {})
|
1123
|
+
def initialize(entities, api_only, shallow, options = {})
|
1064
1124
|
@name = entities.to_s
|
1065
1125
|
@path = (options[:path] || @name).to_s
|
1066
1126
|
@controller = (options[:controller] || @name).to_s
|
1067
1127
|
@as = options[:as]
|
1068
1128
|
@param = (options[:param] || :id).to_sym
|
1069
1129
|
@options = options
|
1070
|
-
@shallow =
|
1130
|
+
@shallow = shallow
|
1131
|
+
@api_only = api_only
|
1132
|
+
@only = options.delete :only
|
1133
|
+
@except = options.delete :except
|
1071
1134
|
end
|
1072
1135
|
|
1073
1136
|
def default_actions
|
1074
|
-
|
1137
|
+
if @api_only
|
1138
|
+
[:index, :create, :show, :update, :destroy]
|
1139
|
+
else
|
1140
|
+
[:index, :create, :new, :show, :update, :destroy, :edit]
|
1141
|
+
end
|
1075
1142
|
end
|
1076
1143
|
|
1077
1144
|
def actions
|
1078
|
-
if
|
1079
|
-
Array(only).map(&:to_sym)
|
1080
|
-
elsif
|
1081
|
-
default_actions - Array(except).map(&:to_sym)
|
1145
|
+
if @only
|
1146
|
+
Array(@only).map(&:to_sym)
|
1147
|
+
elsif @except
|
1148
|
+
default_actions - Array(@except).map(&:to_sym)
|
1082
1149
|
else
|
1083
1150
|
default_actions
|
1084
1151
|
end
|
@@ -1105,7 +1172,7 @@ module ActionDispatch
|
|
1105
1172
|
end
|
1106
1173
|
|
1107
1174
|
def resource_scope
|
1108
|
-
|
1175
|
+
controller
|
1109
1176
|
end
|
1110
1177
|
|
1111
1178
|
alias :collection_scope :path
|
@@ -1128,17 +1195,15 @@ module ActionDispatch
|
|
1128
1195
|
"#{path}/:#{nested_param}"
|
1129
1196
|
end
|
1130
1197
|
|
1131
|
-
def shallow=(value)
|
1132
|
-
@shallow = value
|
1133
|
-
end
|
1134
|
-
|
1135
1198
|
def shallow?
|
1136
1199
|
@shallow
|
1137
1200
|
end
|
1201
|
+
|
1202
|
+
def singleton?; false; end
|
1138
1203
|
end
|
1139
1204
|
|
1140
1205
|
class SingletonResource < Resource #:nodoc:
|
1141
|
-
def initialize(entities, options)
|
1206
|
+
def initialize(entities, api_only, shallow, options)
|
1142
1207
|
super
|
1143
1208
|
@as = nil
|
1144
1209
|
@controller = (options[:controller] || plural).to_s
|
@@ -1146,7 +1211,11 @@ module ActionDispatch
|
|
1146
1211
|
end
|
1147
1212
|
|
1148
1213
|
def default_actions
|
1149
|
-
|
1214
|
+
if @api_only
|
1215
|
+
[:show, :create, :update, :destroy]
|
1216
|
+
else
|
1217
|
+
[:show, :create, :update, :destroy, :new, :edit]
|
1218
|
+
end
|
1150
1219
|
end
|
1151
1220
|
|
1152
1221
|
def plural
|
@@ -1162,6 +1231,8 @@ module ActionDispatch
|
|
1162
1231
|
|
1163
1232
|
alias :member_scope :path
|
1164
1233
|
alias :nested_scope :path
|
1234
|
+
|
1235
|
+
def singleton?; true; end
|
1165
1236
|
end
|
1166
1237
|
|
1167
1238
|
def resources_path_names(options)
|
@@ -1196,20 +1267,23 @@ module ActionDispatch
|
|
1196
1267
|
return self
|
1197
1268
|
end
|
1198
1269
|
|
1199
|
-
|
1200
|
-
|
1270
|
+
with_scope_level(:resource) do
|
1271
|
+
options = apply_action_options options
|
1272
|
+
resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1273
|
+
yield if block_given?
|
1201
1274
|
|
1202
|
-
|
1275
|
+
concerns(options[:concerns]) if options[:concerns]
|
1203
1276
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1277
|
+
collection do
|
1278
|
+
post :create
|
1279
|
+
end if parent_resource.actions.include?(:create)
|
1207
1280
|
|
1208
|
-
|
1209
|
-
|
1210
|
-
|
1281
|
+
new do
|
1282
|
+
get :new
|
1283
|
+
end if parent_resource.actions.include?(:new)
|
1211
1284
|
|
1212
|
-
|
1285
|
+
set_member_mappings_for_resource
|
1286
|
+
end
|
1213
1287
|
end
|
1214
1288
|
|
1215
1289
|
self
|
@@ -1354,21 +1428,24 @@ module ActionDispatch
|
|
1354
1428
|
return self
|
1355
1429
|
end
|
1356
1430
|
|
1357
|
-
|
1358
|
-
|
1431
|
+
with_scope_level(:resources) do
|
1432
|
+
options = apply_action_options options
|
1433
|
+
resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
|
1434
|
+
yield if block_given?
|
1359
1435
|
|
1360
|
-
|
1436
|
+
concerns(options[:concerns]) if options[:concerns]
|
1361
1437
|
|
1362
|
-
|
1363
|
-
|
1364
|
-
|
1365
|
-
|
1438
|
+
collection do
|
1439
|
+
get :index if parent_resource.actions.include?(:index)
|
1440
|
+
post :create if parent_resource.actions.include?(:create)
|
1441
|
+
end
|
1366
1442
|
|
1367
|
-
|
1368
|
-
|
1369
|
-
|
1443
|
+
new do
|
1444
|
+
get :new
|
1445
|
+
end if parent_resource.actions.include?(:new)
|
1370
1446
|
|
1371
|
-
|
1447
|
+
set_member_mappings_for_resource
|
1448
|
+
end
|
1372
1449
|
end
|
1373
1450
|
|
1374
1451
|
self
|
@@ -1392,7 +1469,7 @@ module ActionDispatch
|
|
1392
1469
|
end
|
1393
1470
|
|
1394
1471
|
with_scope_level(:collection) do
|
1395
|
-
|
1472
|
+
path_scope(parent_resource.collection_scope) do
|
1396
1473
|
yield
|
1397
1474
|
end
|
1398
1475
|
end
|
@@ -1416,9 +1493,11 @@ module ActionDispatch
|
|
1416
1493
|
|
1417
1494
|
with_scope_level(:member) do
|
1418
1495
|
if shallow?
|
1419
|
-
shallow_scope
|
1496
|
+
shallow_scope {
|
1497
|
+
path_scope(parent_resource.member_scope) { yield }
|
1498
|
+
}
|
1420
1499
|
else
|
1421
|
-
|
1500
|
+
path_scope(parent_resource.member_scope) { yield }
|
1422
1501
|
end
|
1423
1502
|
end
|
1424
1503
|
end
|
@@ -1429,7 +1508,7 @@ module ActionDispatch
|
|
1429
1508
|
end
|
1430
1509
|
|
1431
1510
|
with_scope_level(:new) do
|
1432
|
-
|
1511
|
+
path_scope(parent_resource.new_scope(action_path(:new))) do
|
1433
1512
|
yield
|
1434
1513
|
end
|
1435
1514
|
end
|
@@ -1442,9 +1521,15 @@ module ActionDispatch
|
|
1442
1521
|
|
1443
1522
|
with_scope_level(:nested) do
|
1444
1523
|
if shallow? && shallow_nesting_depth >= 1
|
1445
|
-
shallow_scope
|
1524
|
+
shallow_scope do
|
1525
|
+
path_scope(parent_resource.nested_scope) do
|
1526
|
+
scope(nested_options) { yield }
|
1527
|
+
end
|
1528
|
+
end
|
1446
1529
|
else
|
1447
|
-
|
1530
|
+
path_scope(parent_resource.nested_scope) do
|
1531
|
+
scope(nested_options) { yield }
|
1532
|
+
end
|
1448
1533
|
end
|
1449
1534
|
end
|
1450
1535
|
end
|
@@ -1459,18 +1544,22 @@ module ActionDispatch
|
|
1459
1544
|
end
|
1460
1545
|
|
1461
1546
|
def shallow
|
1462
|
-
scope(:
|
1463
|
-
|
1464
|
-
|
1547
|
+
@scope = @scope.new(shallow: true)
|
1548
|
+
yield
|
1549
|
+
ensure
|
1550
|
+
@scope = @scope.parent
|
1465
1551
|
end
|
1466
1552
|
|
1467
1553
|
def shallow?
|
1468
|
-
parent_resource.
|
1554
|
+
!parent_resource.singleton? && @scope[:shallow]
|
1469
1555
|
end
|
1470
1556
|
|
1471
|
-
#
|
1472
|
-
#
|
1473
|
-
#
|
1557
|
+
# Matches a url pattern to one or more routes.
|
1558
|
+
# For more information, see match[rdoc-ref:Base#match].
|
1559
|
+
#
|
1560
|
+
# match 'path' => 'controller#action', via: patch
|
1561
|
+
# match 'path', to: 'controller#action', via: :post
|
1562
|
+
# match 'path', 'otherpath', on: :member, via: :get
|
1474
1563
|
def match(path, *rest)
|
1475
1564
|
if rest.empty? && Hash === path
|
1476
1565
|
options = path
|
@@ -1496,8 +1585,6 @@ module ActionDispatch
|
|
1496
1585
|
paths = [path] + rest
|
1497
1586
|
end
|
1498
1587
|
|
1499
|
-
options[:anchor] = true unless options.key?(:anchor)
|
1500
|
-
|
1501
1588
|
if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
|
1502
1589
|
raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
|
1503
1590
|
end
|
@@ -1506,48 +1593,85 @@ module ActionDispatch
|
|
1506
1593
|
options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
|
1507
1594
|
end
|
1508
1595
|
|
1509
|
-
|
1596
|
+
controller = options.delete(:controller) || @scope[:controller]
|
1597
|
+
option_path = options.delete :path
|
1598
|
+
to = options.delete :to
|
1599
|
+
via = Mapping.check_via Array(options.delete(:via) {
|
1600
|
+
@scope[:via]
|
1601
|
+
})
|
1602
|
+
formatted = options.delete(:format) { @scope[:format] }
|
1603
|
+
anchor = options.delete(:anchor) { true }
|
1604
|
+
options_constraints = options.delete(:constraints) || {}
|
1605
|
+
|
1606
|
+
path_types = paths.group_by(&:class)
|
1607
|
+
path_types.fetch(String, []).each do |_path|
|
1510
1608
|
route_options = options.dup
|
1511
|
-
|
1609
|
+
if _path && option_path
|
1610
|
+
ActiveSupport::Deprecation.warn <<-eowarn
|
1611
|
+
Specifying strings for both :path and the route path is deprecated. Change things like this:
|
1612
|
+
|
1613
|
+
match #{_path.inspect}, :path => #{option_path.inspect}
|
1614
|
+
|
1615
|
+
to this:
|
1512
1616
|
|
1513
|
-
|
1514
|
-
|
1515
|
-
route_options[:
|
1516
|
-
route_options[:
|
1617
|
+
match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{path.inspect}
|
1618
|
+
eowarn
|
1619
|
+
route_options[:action] = _path
|
1620
|
+
route_options[:as] = _path
|
1621
|
+
_path = option_path
|
1517
1622
|
end
|
1623
|
+
to = get_to_from_path(_path, to, route_options[:action])
|
1624
|
+
decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
|
1625
|
+
end
|
1518
1626
|
|
1519
|
-
|
1627
|
+
path_types.fetch(Symbol, []).each do |action|
|
1628
|
+
route_options = options.dup
|
1629
|
+
decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
|
1520
1630
|
end
|
1631
|
+
|
1521
1632
|
self
|
1522
1633
|
end
|
1523
1634
|
|
1524
|
-
def
|
1525
|
-
|
1635
|
+
def get_to_from_path(path, to, action)
|
1636
|
+
return to if to || action
|
1637
|
+
|
1638
|
+
path_without_format = path.sub(/\(\.:format\)$/, '')
|
1639
|
+
if using_match_shorthand?(path_without_format)
|
1640
|
+
path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
|
1641
|
+
else
|
1642
|
+
nil
|
1643
|
+
end
|
1644
|
+
end
|
1645
|
+
|
1646
|
+
def using_match_shorthand?(path)
|
1647
|
+
path =~ %r{^/?[-\w]+/[-\w/]+$}
|
1526
1648
|
end
|
1527
1649
|
|
1528
|
-
def decomposed_match(path, options) # :nodoc:
|
1650
|
+
def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
|
1529
1651
|
if on = options.delete(:on)
|
1530
|
-
send(on) { decomposed_match(path, options) }
|
1652
|
+
send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1531
1653
|
else
|
1532
1654
|
case @scope.scope_level
|
1533
1655
|
when :resources
|
1534
|
-
nested { decomposed_match(path, options) }
|
1656
|
+
nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1535
1657
|
when :resource
|
1536
|
-
member { decomposed_match(path, options) }
|
1658
|
+
member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
|
1537
1659
|
else
|
1538
|
-
add_route(path, options)
|
1660
|
+
add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
|
1539
1661
|
end
|
1540
1662
|
end
|
1541
1663
|
end
|
1542
1664
|
|
1543
|
-
def add_route(action, options) # :nodoc:
|
1544
|
-
path = path_for_action(action,
|
1665
|
+
def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
|
1666
|
+
path = path_for_action(action, _path)
|
1545
1667
|
raise ArgumentError, "path is required" if path.blank?
|
1546
1668
|
|
1547
|
-
action = action.to_s
|
1669
|
+
action = action.to_s
|
1670
|
+
|
1671
|
+
default_action = options.delete(:action) || @scope[:action]
|
1548
1672
|
|
1549
1673
|
if action =~ /^[\w\-\/]+$/
|
1550
|
-
|
1674
|
+
default_action ||= action.tr('-', '_') unless action.include?("/")
|
1551
1675
|
else
|
1552
1676
|
action = nil
|
1553
1677
|
end
|
@@ -1558,9 +1682,11 @@ module ActionDispatch
|
|
1558
1682
|
name_for_action(options.delete(:as), action)
|
1559
1683
|
end
|
1560
1684
|
|
1561
|
-
|
1562
|
-
|
1563
|
-
|
1685
|
+
path = Mapping.normalize_path URI.parser.escape(path), formatted
|
1686
|
+
ast = Journey::Parser.parse path
|
1687
|
+
|
1688
|
+
mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
|
1689
|
+
@set.add_route(mapping, ast, as, anchor)
|
1564
1690
|
end
|
1565
1691
|
|
1566
1692
|
def root(path, options={})
|
@@ -1574,7 +1700,7 @@ module ActionDispatch
|
|
1574
1700
|
|
1575
1701
|
if @scope.resources?
|
1576
1702
|
with_scope_level(:root) do
|
1577
|
-
|
1703
|
+
path_scope(parent_resource.path) do
|
1578
1704
|
super(options)
|
1579
1705
|
end
|
1580
1706
|
end
|
@@ -1619,23 +1745,20 @@ module ActionDispatch
|
|
1619
1745
|
return true
|
1620
1746
|
end
|
1621
1747
|
|
1622
|
-
unless action_options?(options)
|
1623
|
-
options.merge!(scope_action_options) if scope_action_options?
|
1624
|
-
end
|
1625
|
-
|
1626
1748
|
false
|
1627
1749
|
end
|
1628
1750
|
|
1629
|
-
def
|
1630
|
-
options
|
1751
|
+
def apply_action_options(options) # :nodoc:
|
1752
|
+
return options if action_options? options
|
1753
|
+
options.merge scope_action_options
|
1631
1754
|
end
|
1632
1755
|
|
1633
|
-
def
|
1634
|
-
|
1756
|
+
def action_options?(options) #:nodoc:
|
1757
|
+
options[:only] || options[:except]
|
1635
1758
|
end
|
1636
1759
|
|
1637
1760
|
def scope_action_options #:nodoc:
|
1638
|
-
@scope[:
|
1761
|
+
@scope[:action_options] || {}
|
1639
1762
|
end
|
1640
1763
|
|
1641
1764
|
def resource_scope? #:nodoc:
|
@@ -1650,18 +1773,6 @@ module ActionDispatch
|
|
1650
1773
|
@scope.nested?
|
1651
1774
|
end
|
1652
1775
|
|
1653
|
-
def with_exclusive_scope
|
1654
|
-
begin
|
1655
|
-
@scope = @scope.new(:as => nil, :path => nil)
|
1656
|
-
|
1657
|
-
with_scope_level(:exclusive) do
|
1658
|
-
yield
|
1659
|
-
end
|
1660
|
-
ensure
|
1661
|
-
@scope = @scope.parent
|
1662
|
-
end
|
1663
|
-
end
|
1664
|
-
|
1665
1776
|
def with_scope_level(kind)
|
1666
1777
|
@scope = @scope.new_level(kind)
|
1667
1778
|
yield
|
@@ -1669,16 +1780,11 @@ module ActionDispatch
|
|
1669
1780
|
@scope = @scope.parent
|
1670
1781
|
end
|
1671
1782
|
|
1672
|
-
def resource_scope(
|
1673
|
-
resource.shallow = @scope[:shallow]
|
1783
|
+
def resource_scope(resource) #:nodoc:
|
1674
1784
|
@scope = @scope.new(:scope_level_resource => resource)
|
1675
|
-
@nesting.push(resource)
|
1676
1785
|
|
1677
|
-
|
1678
|
-
scope(parent_resource.resource_scope) { yield }
|
1679
|
-
end
|
1786
|
+
controller(resource.resource_scope) { yield }
|
1680
1787
|
ensure
|
1681
|
-
@nesting.pop
|
1682
1788
|
@scope = @scope.parent
|
1683
1789
|
end
|
1684
1790
|
|
@@ -1691,12 +1797,10 @@ module ActionDispatch
|
|
1691
1797
|
options
|
1692
1798
|
end
|
1693
1799
|
|
1694
|
-
def nesting_depth #:nodoc:
|
1695
|
-
@nesting.size
|
1696
|
-
end
|
1697
|
-
|
1698
1800
|
def shallow_nesting_depth #:nodoc:
|
1699
|
-
@
|
1801
|
+
@scope.find_all { |node|
|
1802
|
+
node.frame[:scope_level_resource]
|
1803
|
+
}.count { |node| node.frame[:scope_level_resource].shallow? }
|
1700
1804
|
end
|
1701
1805
|
|
1702
1806
|
def param_constraint? #:nodoc:
|
@@ -1711,27 +1815,28 @@ module ActionDispatch
|
|
1711
1815
|
resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
|
1712
1816
|
end
|
1713
1817
|
|
1714
|
-
def shallow_scope
|
1818
|
+
def shallow_scope #:nodoc:
|
1715
1819
|
scope = { :as => @scope[:shallow_prefix],
|
1716
1820
|
:path => @scope[:shallow_path] }
|
1717
1821
|
@scope = @scope.new scope
|
1718
1822
|
|
1719
|
-
|
1823
|
+
yield
|
1720
1824
|
ensure
|
1721
1825
|
@scope = @scope.parent
|
1722
1826
|
end
|
1723
1827
|
|
1724
1828
|
def path_for_action(action, path) #:nodoc:
|
1725
|
-
|
1829
|
+
return "#{@scope[:path]}/#{path}" if path
|
1830
|
+
|
1831
|
+
if canonical_action?(action)
|
1726
1832
|
@scope[:path].to_s
|
1727
1833
|
else
|
1728
|
-
"#{@scope[:path]}/#{action_path(action
|
1834
|
+
"#{@scope[:path]}/#{action_path(action)}"
|
1729
1835
|
end
|
1730
1836
|
end
|
1731
1837
|
|
1732
|
-
def action_path(name
|
1733
|
-
name
|
1734
|
-
path || @scope[:path_names][name] || name.to_s
|
1838
|
+
def action_path(name) #:nodoc:
|
1839
|
+
@scope[:path_names][name.to_sym] || name
|
1735
1840
|
end
|
1736
1841
|
|
1737
1842
|
def prefix_name_for_action(as, action) #:nodoc:
|
@@ -1757,14 +1862,15 @@ module ActionDispatch
|
|
1757
1862
|
member_name = parent_resource.member_name
|
1758
1863
|
end
|
1759
1864
|
|
1760
|
-
|
1865
|
+
action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
|
1866
|
+
candidate = action_name.select(&:present?).join('_')
|
1761
1867
|
|
1762
|
-
|
1868
|
+
unless candidate.empty?
|
1763
1869
|
# If a name was not explicitly given, we check if it is valid
|
1764
1870
|
# and return nil in case it isn't. Otherwise, we pass the invalid name
|
1765
1871
|
# forward so the underlying router engine treats it and raises an exception.
|
1766
1872
|
if as.nil?
|
1767
|
-
candidate unless candidate !~ /\A[_a-z]/i ||
|
1873
|
+
candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
|
1768
1874
|
else
|
1769
1875
|
candidate
|
1770
1876
|
end
|
@@ -1782,6 +1888,18 @@ module ActionDispatch
|
|
1782
1888
|
delete :destroy if parent_resource.actions.include?(:destroy)
|
1783
1889
|
end
|
1784
1890
|
end
|
1891
|
+
|
1892
|
+
def api_only?
|
1893
|
+
@set.api_only?
|
1894
|
+
end
|
1895
|
+
private
|
1896
|
+
|
1897
|
+
def path_scope(path)
|
1898
|
+
@scope = @scope.new(path: merge_path_scope(@scope[:path], path))
|
1899
|
+
yield
|
1900
|
+
ensure
|
1901
|
+
@scope = @scope.parent
|
1902
|
+
end
|
1785
1903
|
end
|
1786
1904
|
|
1787
1905
|
# Routing Concerns allow you to declare common routes that can be reused
|
@@ -1892,14 +2010,14 @@ module ActionDispatch
|
|
1892
2010
|
class Scope # :nodoc:
|
1893
2011
|
OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
|
1894
2012
|
:controller, :action, :path_names, :constraints,
|
1895
|
-
:shallow, :blocks, :defaults, :options]
|
2013
|
+
:shallow, :blocks, :defaults, :via, :format, :options]
|
1896
2014
|
|
1897
2015
|
RESOURCE_SCOPES = [:resource, :resources]
|
1898
2016
|
RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
|
1899
2017
|
|
1900
2018
|
attr_reader :parent, :scope_level
|
1901
2019
|
|
1902
|
-
def initialize(hash, parent =
|
2020
|
+
def initialize(hash, parent = NULL, scope_level = nil)
|
1903
2021
|
@hash = hash
|
1904
2022
|
@parent = parent
|
1905
2023
|
@scope_level = scope_level
|
@@ -1947,27 +2065,34 @@ module ActionDispatch
|
|
1947
2065
|
end
|
1948
2066
|
|
1949
2067
|
def new_level(level)
|
1950
|
-
self.class.new(
|
1951
|
-
end
|
1952
|
-
|
1953
|
-
def fetch(key, &block)
|
1954
|
-
@hash.fetch(key, &block)
|
2068
|
+
self.class.new(frame, self, level)
|
1955
2069
|
end
|
1956
2070
|
|
1957
2071
|
def [](key)
|
1958
|
-
|
2072
|
+
scope = find { |node| node.frame.key? key }
|
2073
|
+
scope && scope.frame[key]
|
1959
2074
|
end
|
1960
2075
|
|
1961
|
-
|
1962
|
-
|
2076
|
+
include Enumerable
|
2077
|
+
|
2078
|
+
def each
|
2079
|
+
node = self
|
2080
|
+
loop do
|
2081
|
+
break if node.equal? NULL
|
2082
|
+
yield node
|
2083
|
+
node = node.parent
|
2084
|
+
end
|
1963
2085
|
end
|
2086
|
+
|
2087
|
+
def frame; @hash; end
|
2088
|
+
|
2089
|
+
NULL = Scope.new(nil, nil)
|
1964
2090
|
end
|
1965
2091
|
|
1966
2092
|
def initialize(set) #:nodoc:
|
1967
2093
|
@set = set
|
1968
2094
|
@scope = Scope.new({ :path_names => @set.resources_path_names })
|
1969
2095
|
@concerns = {}
|
1970
|
-
@nesting = []
|
1971
2096
|
end
|
1972
2097
|
|
1973
2098
|
include Base
|