actionpack 4.2.11.3 → 5.0.7.2

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.

Files changed (136) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +890 -384
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/base.rb +28 -38
  6. data/lib/{action_controller → abstract_controller}/caching/fragments.rb +51 -11
  7. data/lib/abstract_controller/caching.rb +62 -0
  8. data/lib/abstract_controller/callbacks.rb +54 -19
  9. data/lib/abstract_controller/collector.rb +4 -9
  10. data/lib/abstract_controller/error.rb +4 -0
  11. data/lib/abstract_controller/helpers.rb +4 -3
  12. data/lib/abstract_controller/railties/routes_helpers.rb +2 -2
  13. data/lib/abstract_controller/rendering.rb +28 -18
  14. data/lib/abstract_controller/translation.rb +8 -7
  15. data/lib/abstract_controller.rb +6 -2
  16. data/lib/action_controller/api/api_rendering.rb +14 -0
  17. data/lib/action_controller/api.rb +147 -0
  18. data/lib/action_controller/base.rb +14 -11
  19. data/lib/action_controller/caching.rb +13 -58
  20. data/lib/action_controller/form_builder.rb +48 -0
  21. data/lib/action_controller/log_subscriber.rb +3 -10
  22. data/lib/action_controller/metal/basic_implicit_render.rb +11 -0
  23. data/lib/action_controller/metal/conditional_get.rb +106 -34
  24. data/lib/action_controller/metal/cookies.rb +1 -3
  25. data/lib/action_controller/metal/data_streaming.rb +14 -34
  26. data/lib/action_controller/metal/etag_with_template_digest.rb +8 -2
  27. data/lib/action_controller/metal/exceptions.rb +11 -6
  28. data/lib/action_controller/metal/force_ssl.rb +11 -11
  29. data/lib/action_controller/metal/head.rb +14 -8
  30. data/lib/action_controller/metal/helpers.rb +15 -6
  31. data/lib/action_controller/metal/http_authentication.rb +44 -35
  32. data/lib/action_controller/metal/implicit_render.rb +61 -6
  33. data/lib/action_controller/metal/instrumentation.rb +5 -5
  34. data/lib/action_controller/metal/live.rb +71 -88
  35. data/lib/action_controller/metal/mime_responds.rb +27 -42
  36. data/lib/action_controller/metal/params_wrapper.rb +9 -9
  37. data/lib/action_controller/metal/redirecting.rb +32 -9
  38. data/lib/action_controller/metal/renderers.rb +83 -40
  39. data/lib/action_controller/metal/rendering.rb +38 -6
  40. data/lib/action_controller/metal/request_forgery_protection.rb +126 -48
  41. data/lib/action_controller/metal/rescue.rb +3 -12
  42. data/lib/action_controller/metal/streaming.rb +4 -4
  43. data/lib/action_controller/metal/strong_parameters.rb +527 -134
  44. data/lib/action_controller/metal/testing.rb +1 -12
  45. data/lib/action_controller/metal/url_for.rb +12 -5
  46. data/lib/action_controller/metal.rb +88 -63
  47. data/lib/action_controller/railtie.rb +11 -7
  48. data/lib/action_controller/renderer.rb +113 -0
  49. data/lib/action_controller/template_assertions.rb +9 -0
  50. data/lib/action_controller/test_case.rb +311 -374
  51. data/lib/action_controller.rb +12 -9
  52. data/lib/action_dispatch/http/cache.rb +73 -34
  53. data/lib/action_dispatch/http/filter_parameters.rb +16 -12
  54. data/lib/action_dispatch/http/filter_redirect.rb +7 -8
  55. data/lib/action_dispatch/http/headers.rb +45 -14
  56. data/lib/action_dispatch/http/mime_negotiation.rb +42 -23
  57. data/lib/action_dispatch/http/mime_type.rb +126 -90
  58. data/lib/action_dispatch/http/mime_types.rb +3 -4
  59. data/lib/action_dispatch/http/parameter_filter.rb +19 -9
  60. data/lib/action_dispatch/http/parameters.rb +70 -40
  61. data/lib/action_dispatch/http/request.rb +144 -89
  62. data/lib/action_dispatch/http/response.rb +215 -102
  63. data/lib/action_dispatch/http/upload.rb +6 -2
  64. data/lib/action_dispatch/http/url.rb +117 -8
  65. data/lib/action_dispatch/journey/formatter.rb +47 -30
  66. data/lib/action_dispatch/journey/gtg/transition_table.rb +1 -1
  67. data/lib/action_dispatch/journey/nfa/dot.rb +0 -2
  68. data/lib/action_dispatch/journey/nfa/transition_table.rb +1 -46
  69. data/lib/action_dispatch/journey/nodes/node.rb +14 -4
  70. data/lib/action_dispatch/journey/parser.rb +2 -0
  71. data/lib/action_dispatch/journey/parser_extras.rb +8 -2
  72. data/lib/action_dispatch/journey/path/pattern.rb +38 -42
  73. data/lib/action_dispatch/journey/route.rb +88 -26
  74. data/lib/action_dispatch/journey/router/utils.rb +5 -5
  75. data/lib/action_dispatch/journey/router.rb +8 -10
  76. data/lib/action_dispatch/journey/routes.rb +14 -15
  77. data/lib/action_dispatch/journey/visitors.rb +89 -44
  78. data/lib/action_dispatch/middleware/callbacks.rb +10 -1
  79. data/lib/action_dispatch/middleware/cookies.rb +188 -134
  80. data/lib/action_dispatch/middleware/debug_exceptions.rb +128 -49
  81. data/lib/action_dispatch/middleware/debug_locks.rb +122 -0
  82. data/lib/action_dispatch/middleware/exception_wrapper.rb +21 -21
  83. data/lib/action_dispatch/middleware/executor.rb +19 -0
  84. data/lib/action_dispatch/middleware/flash.rb +66 -45
  85. data/lib/action_dispatch/middleware/params_parser.rb +32 -46
  86. data/lib/action_dispatch/middleware/public_exceptions.rb +2 -2
  87. data/lib/action_dispatch/middleware/reloader.rb +14 -58
  88. data/lib/action_dispatch/middleware/remote_ip.rb +29 -19
  89. data/lib/action_dispatch/middleware/request_id.rb +11 -6
  90. data/lib/action_dispatch/middleware/session/abstract_store.rb +23 -11
  91. data/lib/action_dispatch/middleware/session/cache_store.rb +9 -6
  92. data/lib/action_dispatch/middleware/session/cookie_store.rb +30 -24
  93. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +4 -0
  94. data/lib/action_dispatch/middleware/show_exceptions.rb +11 -9
  95. data/lib/action_dispatch/middleware/ssl.rb +124 -36
  96. data/lib/action_dispatch/middleware/stack.rb +44 -40
  97. data/lib/action_dispatch/middleware/static.rb +51 -35
  98. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +2 -14
  99. data/lib/action_dispatch/middleware/templates/rescues/_source.text.erb +8 -0
  100. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +1 -1
  101. data/lib/action_dispatch/middleware/templates/rescues/template_error.text.erb +1 -1
  102. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +4 -4
  103. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +59 -63
  104. data/lib/action_dispatch/railtie.rb +2 -2
  105. data/lib/action_dispatch/request/session.rb +69 -33
  106. data/lib/action_dispatch/request/utils.rb +51 -19
  107. data/lib/action_dispatch/routing/inspector.rb +32 -43
  108. data/lib/action_dispatch/routing/mapper.rb +515 -348
  109. data/lib/action_dispatch/routing/polymorphic_routes.rb +8 -14
  110. data/lib/action_dispatch/routing/redirection.rb +5 -4
  111. data/lib/action_dispatch/routing/route_set.rb +148 -240
  112. data/lib/action_dispatch/routing/url_for.rb +27 -10
  113. data/lib/action_dispatch/routing.rb +17 -13
  114. data/lib/action_dispatch/testing/assertion_response.rb +45 -0
  115. data/lib/action_dispatch/testing/assertions/response.rb +38 -20
  116. data/lib/action_dispatch/testing/assertions/routing.rb +16 -12
  117. data/lib/action_dispatch/testing/assertions.rb +1 -1
  118. data/lib/action_dispatch/testing/integration.rb +377 -149
  119. data/lib/action_dispatch/testing/request_encoder.rb +53 -0
  120. data/lib/action_dispatch/testing/test_process.rb +24 -20
  121. data/lib/action_dispatch/testing/test_request.rb +22 -31
  122. data/lib/action_dispatch/testing/test_response.rb +12 -4
  123. data/lib/action_dispatch.rb +4 -1
  124. data/lib/action_pack/gem_version.rb +4 -4
  125. data/lib/action_pack.rb +1 -1
  126. metadata +32 -34
  127. data/lib/action_controller/metal/hide_actions.rb +0 -40
  128. data/lib/action_controller/metal/rack_delegation.rb +0 -32
  129. data/lib/action_controller/middleware.rb +0 -39
  130. data/lib/action_controller/model_naming.rb +0 -12
  131. data/lib/action_dispatch/journey/backwards.rb +0 -5
  132. data/lib/action_dispatch/journey/router/strexp.rb +0 -27
  133. data/lib/action_dispatch/testing/assertions/dom.rb +0 -3
  134. data/lib/action_dispatch/testing/assertions/selector.rb +0 -3
  135. data/lib/action_dispatch/testing/assertions/tag.rb +0 -3
  136. /data/lib/action_dispatch/middleware/templates/rescues/{_source.erb → _source.html.erb} +0 -0
@@ -1,24 +1,22 @@
1
- require 'active_support/core_ext/hash/except'
2
- require 'active_support/core_ext/hash/reverse_merge'
3
1
  require 'active_support/core_ext/hash/slice'
4
2
  require 'active_support/core_ext/enumerable'
5
3
  require 'active_support/core_ext/array/extract_options'
6
- require 'active_support/core_ext/module/remove_method'
7
- require 'active_support/core_ext/string/filters'
8
- require 'active_support/inflector'
4
+ require 'active_support/core_ext/regexp'
9
5
  require 'action_dispatch/routing/redirection'
10
6
  require 'action_dispatch/routing/endpoint'
11
- require 'active_support/deprecation'
12
7
 
13
8
  module ActionDispatch
14
9
  module Routing
15
10
  class Mapper
16
11
  URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
17
12
 
18
- class Constraints < Endpoint #:nodoc:
13
+ class Constraints < Routing::Endpoint #:nodoc:
19
14
  attr_reader :app, :constraints
20
15
 
21
- def initialize(app, constraints, dispatcher_p)
16
+ SERVE = ->(app, req) { app.serve req }
17
+ CALL = ->(app, req) { app.call req.env }
18
+
19
+ def initialize(app, constraints, strategy)
22
20
  # Unwrap Constraints objects. I don't actually think it's possible
23
21
  # to pass a Constraints object to this constructor, but there were
24
22
  # multiple places that kept testing children of this object. I
@@ -28,12 +26,12 @@ module ActionDispatch
28
26
  app = app.app
29
27
  end
30
28
 
31
- @dispatcher = dispatcher_p
29
+ @strategy = strategy
32
30
 
33
31
  @app, @constraints, = app, constraints
34
32
  end
35
33
 
36
- def dispatcher?; @dispatcher; end
34
+ def dispatcher?; @strategy == SERVE; end
37
35
 
38
36
  def matches?(req)
39
37
  @constraints.all? do |constraint|
@@ -45,11 +43,7 @@ module ActionDispatch
45
43
  def serve(req)
46
44
  return [ 404, {'X-Cascade' => 'pass'}, [] ] unless matches?(req)
47
45
 
48
- if dispatcher?
49
- @app.serve req
50
- else
51
- @app.call req.env
52
- end
46
+ @strategy.call @app, req
53
47
  end
54
48
 
55
49
  private
@@ -62,101 +56,180 @@ module ActionDispatch
62
56
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
63
57
  OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
64
58
 
65
- attr_reader :requirements, :conditions, :defaults
66
- attr_reader :to, :default_controller, :default_action, :as, :anchor
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, path, as, options)
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
- options.delete :only
72
- options.delete :except
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 = (scope[:defaults] || {}).merge options.delete(: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
- new scope, set, path, defaults, as, options
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 initialize(scope, set, path, defaults, as, options)
83
- @requirements, @conditions = {}, {}
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
95
+
96
+ def self.optional_format?(path, format)
97
+ format != false && path !~ OPTIONAL_FORMAT_REGEX
98
+ end
99
+
100
+ def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
84
101
  @defaults = defaults
85
102
  @set = set
86
103
 
87
- @to = options.delete :to
88
- @default_controller = options.delete(:controller) || scope[:controller]
89
- @default_action = options.delete(:action) || scope[:action]
90
- @as = as
91
- @anchor = options.delete :anchor
104
+ @to = to
105
+ @default_controller = controller
106
+ @default_action = default_action
107
+ @ast = ast
108
+ @anchor = anchor
109
+ @via = via
110
+ @internal = options[:internal]
92
111
 
93
- formatted = options.delete :format
94
- via = Array(options.delete(:via) { [] })
95
- options_constraints = options.delete :constraints
112
+ path_params = ast.find_all(&:symbol?).map(&:to_sym)
96
113
 
97
- path = normalize_path! path, formatted
98
- ast = path_ast path
99
- path_params = path_params ast
114
+ options = add_wildcard_options(options, formatted, ast)
100
115
 
101
- options = normalize_options!(options, formatted, path_params, ast, scope[:module])
116
+ options = normalize_options!(options, path_params, modyoule)
102
117
 
118
+ split_options = constraints(options, path_params)
119
+
120
+ constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
121
+
122
+ if options_constraints.is_a?(Hash)
123
+ @defaults = Hash[options_constraints.find_all { |key, default|
124
+ URL_OPTIONS.include?(key) && (String === default || Integer === default)
125
+ }].merge @defaults
126
+ @blocks = blocks
127
+ constraints.merge! options_constraints
128
+ else
129
+ @blocks = blocks(options_constraints)
130
+ end
103
131
 
104
- split_constraints(path_params, scope[:constraints]) if scope[:constraints]
105
- constraints = constraints(options, path_params)
132
+ requirements, conditions = split_constraints path_params, constraints
133
+ verify_regexp_requirements requirements.map(&:last).grep(Regexp)
106
134
 
107
- split_constraints path_params, constraints
135
+ formats = normalize_format(formatted)
108
136
 
109
- @blocks = blocks(options_constraints, scope[:blocks])
137
+ @requirements = formats[:requirements].merge Hash[requirements]
138
+ @conditions = Hash[conditions]
139
+ @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
110
140
 
111
- if options_constraints.is_a?(Hash)
112
- split_constraints path_params, options_constraints
113
- options_constraints.each do |key, default|
114
- if URL_OPTIONS.include?(key) && (String === default || Integer === default)
115
- @defaults[key] ||= default
116
- end
117
- end
141
+ if path_params.include?(:action) && !@requirements.key?(:action)
142
+ @defaults[:action] ||= 'index'
118
143
  end
119
144
 
120
- normalize_format!(formatted)
145
+ @required_defaults = (split_options[:required_defaults] || []).map(&:first)
146
+ end
121
147
 
122
- @conditions[:path_info] = path
123
- @conditions[:parsed_path_info] = ast
148
+ def make_route(name, precedence)
149
+ route = Journey::Route.new(name,
150
+ application,
151
+ path,
152
+ conditions,
153
+ required_defaults,
154
+ defaults,
155
+ request_method,
156
+ precedence,
157
+ @internal)
158
+
159
+ route
160
+ end
124
161
 
125
- add_request_method(via, @conditions)
126
- normalize_defaults!(options)
162
+ def application
163
+ app(@blocks)
127
164
  end
128
165
 
129
- def to_route
130
- [ app(@blocks), conditions, requirements, defaults, as, anchor ]
166
+ def path
167
+ build_path @ast, requirements, @anchor
131
168
  end
132
169
 
133
- private
170
+ def conditions
171
+ build_conditions @conditions, @set.request_class
172
+ end
173
+
174
+ def build_conditions(current_conditions, request_class)
175
+ conditions = current_conditions.dup
134
176
 
135
- def normalize_path!(path, format)
136
- path = Mapper.normalize_path(path)
177
+ conditions.keep_if do |k, _|
178
+ request_class.public_method_defined?(k)
179
+ end
180
+ end
181
+ private :build_conditions
137
182
 
138
- if format == true
139
- "#{path}.:format"
140
- elsif optional_format?(path, format)
141
- "#{path}(.:format)"
183
+ def request_method
184
+ @via.map { |x| Journey::Route.verb_matcher(x) }
185
+ end
186
+ private :request_method
187
+
188
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
189
+
190
+ def build_path(ast, requirements, anchor)
191
+ pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
192
+
193
+ # Find all the symbol nodes that are adjacent to literal nodes and alter
194
+ # the regexp so that Journey will partition them into custom routes.
195
+ ast.find_all { |node|
196
+ next unless node.cat?
197
+
198
+ if node.left.literal? && node.right.symbol?
199
+ symbol = node.right
200
+ elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
201
+ symbol = node.right.left
202
+ elsif node.left.symbol? && node.right.literal?
203
+ symbol = node.left
204
+ elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
205
+ symbol = node.left
142
206
  else
143
- path
207
+ next
144
208
  end
145
- end
146
209
 
147
- def optional_format?(path, format)
148
- format != false && path !~ OPTIONAL_FORMAT_REGEX
149
- end
210
+ if symbol
211
+ symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
212
+ end
213
+ }
214
+
215
+ pattern
216
+ end
217
+ private :build_path
150
218
 
151
- def normalize_options!(options, formatted, path_params, path_ast, modyoule)
219
+ private
220
+ def add_wildcard_options(options, formatted, path_ast)
152
221
  # Add a constraint for wildcard route to make it non-greedy and match the
153
222
  # optional format part of the route by default
154
223
  if formatted != false
155
- path_ast.grep(Journey::Nodes::Star) do |node|
156
- options[node.name.to_sym] ||= /.+?/
157
- end
224
+ path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
225
+ hash[node.name.to_sym] ||= /.+?/
226
+ }.merge options
227
+ else
228
+ options
158
229
  end
230
+ end
159
231
 
232
+ def normalize_options!(options, path_params, modyoule)
160
233
  if path_params.include?(:controller)
161
234
  raise ArgumentError, ":controller segment is not allowed within a namespace block" if modyoule
162
235
 
@@ -181,74 +254,54 @@ module ActionDispatch
181
254
  end
182
255
 
183
256
  def split_constraints(path_params, constraints)
184
- constraints.each_pair do |key, requirement|
185
- if path_params.include?(key) || key == :controller
186
- verify_regexp_requirement(requirement) if requirement.is_a?(Regexp)
187
- @requirements[key] = requirement
188
- else
189
- @conditions[key] = requirement
190
- end
257
+ constraints.partition do |key, requirement|
258
+ path_params.include?(key) || key == :controller
191
259
  end
192
260
  end
193
261
 
194
- def normalize_format!(formatted)
195
- if formatted == true
196
- @requirements[:format] ||= /.+/
197
- elsif Regexp === formatted
198
- @requirements[:format] = formatted
199
- @defaults[:format] = nil
200
- elsif String === formatted
201
- @requirements[:format] = Regexp.compile(formatted)
202
- @defaults[:format] = formatted
203
- end
204
- end
205
-
206
- def verify_regexp_requirement(requirement)
207
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
208
- raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
209
- end
210
-
211
- if requirement.multiline?
212
- raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
262
+ def normalize_format(formatted)
263
+ case formatted
264
+ when true
265
+ { requirements: { format: /.+/ },
266
+ defaults: {} }
267
+ when Regexp
268
+ { requirements: { format: formatted },
269
+ defaults: { format: nil } }
270
+ when String
271
+ { requirements: { format: Regexp.compile(formatted) },
272
+ defaults: { format: formatted } }
273
+ else
274
+ { requirements: { }, defaults: { } }
213
275
  end
214
276
  end
215
277
 
216
- def normalize_defaults!(options)
217
- options.each_pair do |key, default|
218
- unless Regexp === default
219
- @defaults[key] = default
278
+ def verify_regexp_requirements(requirements)
279
+ requirements.each do |requirement|
280
+ if requirement.source =~ ANCHOR_CHARACTERS_REGEX
281
+ raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
220
282
  end
221
- end
222
- end
223
283
 
224
- def verify_callable_constraint(callable_constraint)
225
- unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
226
- raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
284
+ if requirement.multiline?
285
+ raise ArgumentError, "Regexp multiline option is not allowed in routing requirements: #{requirement.inspect}"
286
+ end
227
287
  end
228
288
  end
229
289
 
230
- def add_request_method(via, conditions)
231
- return if via == [:all]
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 }
290
+ def normalize_defaults(options)
291
+ Hash[options.reject { |_, default| Regexp === default }]
243
292
  end
244
293
 
245
294
  def app(blocks)
246
- if to.respond_to?(:call)
247
- Constraints.new(to, blocks, false)
248
- elsif blocks.any?
249
- Constraints.new(dispatcher(defaults), blocks, true)
295
+ if to.is_a?(Class) && to < ActionController::Metal
296
+ Routing::RouteSet::StaticDispatcher.new to
250
297
  else
251
- dispatcher(defaults)
298
+ if to.respond_to?(:call)
299
+ Constraints.new(to, blocks, Constraints::CALL)
300
+ elsif blocks.any?
301
+ Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE)
302
+ else
303
+ dispatcher(defaults.key?(:controller))
304
+ end
252
305
  end
253
306
  end
254
307
 
@@ -280,22 +333,8 @@ module ActionDispatch
280
333
  end
281
334
 
282
335
  def split_to(to)
283
- case to
284
- when Symbol
285
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
286
- Defining a route where `to` is a symbol is deprecated.
287
- Please change `to: :#{to}` to `action: :#{to}`.
288
- MSG
289
-
290
- [nil, to.to_s]
291
- when /#/ then to.split('#')
292
- when String
293
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
294
- Defining a route where `to` is a controller without an action is deprecated.
295
- Please change `to: '#{to}'` to `controller: '#{to}'`.
296
- MSG
297
-
298
- [to, nil]
336
+ if to =~ /#/
337
+ to.split('#')
299
338
  else
300
339
  []
301
340
  end
@@ -320,40 +359,29 @@ module ActionDispatch
320
359
  yield
321
360
  end
322
361
 
323
- def blocks(options_constraints, scope_blocks)
324
- if options_constraints && !options_constraints.is_a?(Hash)
325
- verify_callable_constraint(options_constraints)
326
- [options_constraints]
327
- else
328
- scope_blocks || []
362
+ def blocks(callable_constraint)
363
+ unless callable_constraint.respond_to?(:call) || callable_constraint.respond_to?(:matches?)
364
+ raise ArgumentError, "Invalid constraint: #{callable_constraint.inspect} must respond to :call or :matches?"
329
365
  end
366
+ [callable_constraint]
330
367
  end
331
368
 
332
369
  def constraints(options, path_params)
333
- constraints = {}
334
- required_defaults = []
335
- options.each_pair do |key, option|
370
+ options.group_by do |key, option|
336
371
  if Regexp === option
337
- constraints[key] = option
372
+ :constraints
338
373
  else
339
- required_defaults << key unless path_params.include?(key)
374
+ if path_params.include?(key)
375
+ :path_params
376
+ else
377
+ :required_defaults
378
+ end
340
379
  end
341
380
  end
342
- @conditions[:required_defaults] = required_defaults
343
- constraints
344
381
  end
345
382
 
346
- def path_params(ast)
347
- ast.grep(Journey::Nodes::Symbol).map { |n| n.name.to_sym }
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
383
+ def dispatcher(raise_on_name_error)
384
+ Routing::RouteSet::Dispatcher.new raise_on_name_error
357
385
  end
358
386
  end
359
387
 
@@ -371,23 +399,6 @@ module ActionDispatch
371
399
  end
372
400
 
373
401
  module Base
374
- # You can specify what Rails should route "/" to with the root method:
375
- #
376
- # root to: 'pages#main'
377
- #
378
- # For options, see +match+, as +root+ uses it internally.
379
- #
380
- # You can also pass a string which will expand
381
- #
382
- # root 'pages#main'
383
- #
384
- # You should put the root route at the top of <tt>config/routes.rb</tt>,
385
- # because this means it will be matched first. As this is the most popular route
386
- # of most Rails applications, this is beneficial.
387
- def root(options = {})
388
- match '/', { :as => :root, :via => :get }.merge!(options)
389
- end
390
-
391
402
  # Matches a url pattern to one or more routes.
392
403
  #
393
404
  # You should not use the +match+ method in your router
@@ -435,7 +446,7 @@ module ActionDispatch
435
446
  # A pattern can also point to a +Rack+ endpoint i.e. anything that
436
447
  # responds to +call+:
437
448
  #
438
- # match 'photos/:id', to: lambda {|hash| [200, {}, ["Coming soon"]] }, via: :get
449
+ # match 'photos/:id', to: -> (hash) { [200, {}, ["Coming soon"]] }, via: :get
439
450
  # match 'photos/:id', to: PhotoRackApp, via: :get
440
451
  # # Yes, controller actions are just rack endpoints
441
452
  # match 'photos/:id', to: PhotosController.action(:show), via: :get
@@ -460,6 +471,21 @@ module ActionDispatch
460
471
  # dynamic segment used to generate the routes).
461
472
  # You can access that segment from your controller using
462
473
  # <tt>params[<:param>]</tt>.
474
+ # In your router:
475
+ #
476
+ # resources :user, param: :name
477
+ #
478
+ # You can override <tt>ActiveRecord::Base#to_param</tt> of a related
479
+ # model to construct a URL:
480
+ #
481
+ # class User < ActiveRecord::Base
482
+ # def to_param
483
+ # name
484
+ # end
485
+ # end
486
+ #
487
+ # user = User.find_by(name: 'Phusion')
488
+ # user_path(user) # => "/users/Phusion"
463
489
  #
464
490
  # [:path]
465
491
  # The path prefix for the routes.
@@ -487,7 +513,7 @@ module ActionDispatch
487
513
  # +call+ or a string representing a controller's action.
488
514
  #
489
515
  # match 'path', to: 'controller#action', via: :get
490
- # match 'path', to: lambda { |env| [200, {}, ["Success!"]] }, via: :get
516
+ # match 'path', to: -> (env) { [200, {}, ["Success!"]] }, via: :get
491
517
  # match 'path', to: RackApp, via: :get
492
518
  #
493
519
  # [:on]
@@ -568,17 +594,20 @@ module ActionDispatch
568
594
  def mount(app, options = nil)
569
595
  if options
570
596
  path = options.delete(:at)
571
- else
572
- unless Hash === app
573
- raise ArgumentError, "must be called with mount point"
574
- end
575
-
597
+ elsif Hash === app
576
598
  options = app
577
599
  app, path = options.find { |k, _| k.respond_to?(:call) }
578
600
  options.delete(app) if app
579
601
  end
580
602
 
581
- raise "A rack application must be specified" unless path
603
+ raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
604
+ raise ArgumentError, <<-MSG.strip_heredoc unless path
605
+ Must be called with mount point
606
+
607
+ mount SomeRackApp, at: "some_route"
608
+ or
609
+ mount(SomeRackApp => "some_route")
610
+ MSG
582
611
 
583
612
  rails_app = rails_app? app
584
613
  options[:as] ||= app_name(app, rails_app)
@@ -605,7 +634,7 @@ module ActionDispatch
605
634
 
606
635
  # Query if the following named route was already defined.
607
636
  def has_named_route?(name)
608
- @set.named_routes.routes[name.to_sym]
637
+ @set.named_routes.key? name
609
638
  end
610
639
 
611
640
  private
@@ -795,21 +824,30 @@ module ActionDispatch
795
824
  URL_OPTIONS.include?(k) && (v.is_a?(String) || v.is_a?(Integer))
796
825
  end
797
826
 
798
- (options[:defaults] ||= {}).reverse_merge!(defaults)
827
+ options[:defaults] = defaults.merge(options[:defaults] || {})
799
828
  else
800
829
  block, options[:constraints] = options[:constraints], {}
801
830
  end
802
831
 
832
+ if options.key?(:only) || options.key?(:except)
833
+ scope[:action_options] = { only: options.delete(:only),
834
+ except: options.delete(:except) }
835
+ end
836
+
837
+ if options.key? :anchor
838
+ raise ArgumentError, 'anchor is ignored unless passed to `match`'
839
+ end
840
+
803
841
  @scope.options.each do |option|
804
842
  if option == :blocks
805
843
  value = block
806
844
  elsif option == :options
807
845
  value = options
808
846
  else
809
- value = options.delete(option)
847
+ value = options.delete(option) { POISON }
810
848
  end
811
849
 
812
- if value
850
+ unless POISON == value
813
851
  scope[option] = send("merge_#{option}_scope", @scope[option], value)
814
852
  end
815
853
  end
@@ -821,14 +859,27 @@ module ActionDispatch
821
859
  @scope = @scope.parent
822
860
  end
823
861
 
862
+ POISON = Object.new # :nodoc:
863
+
824
864
  # Scopes routes to a specific controller
825
865
  #
826
866
  # controller "food" do
827
- # match "bacon", action: "bacon"
867
+ # match "bacon", action: :bacon, via: :get
828
868
  # end
829
- def controller(controller, options={})
830
- options[:controller] = controller
831
- scope(options) { yield }
869
+ def controller(controller, options = {})
870
+ if options.empty?
871
+ begin
872
+ @scope = @scope.new(controller: controller)
873
+ yield
874
+ ensure
875
+ @scope = @scope.parent
876
+ end
877
+ else
878
+ ActiveSupport::Deprecation.warn "#controller with options is deprecated. If you need to pass more options than the controller name use #scope."
879
+
880
+ options[:controller] = controller
881
+ scope(options) { yield }
882
+ end
832
883
  end
833
884
 
834
885
  # Scopes routes to a specific namespace. For example:
@@ -874,13 +925,14 @@ module ActionDispatch
874
925
 
875
926
  defaults = {
876
927
  module: path,
877
- path: options.fetch(:path, path),
878
928
  as: options.fetch(:as, path),
879
929
  shallow_path: options.fetch(:path, path),
880
930
  shallow_prefix: options.fetch(:as, path)
881
931
  }
882
932
 
883
- scope(defaults.merge!(options)) { yield }
933
+ path_scope(options.delete(:path) { path }) do
934
+ scope(defaults.merge!(options)) { yield }
935
+ end
884
936
  end
885
937
 
886
938
  # === Parameter Restriction
@@ -917,7 +969,7 @@ module ActionDispatch
917
969
  #
918
970
  # Requests to routes can be constrained based on specific criteria:
919
971
  #
920
- # constraints(lambda { |req| req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
972
+ # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
921
973
  # resources :iphones
922
974
  # end
923
975
  #
@@ -948,7 +1000,10 @@ module ActionDispatch
948
1000
  # end
949
1001
  # Using this, the +:id+ parameter here will default to 'home'.
950
1002
  def defaults(defaults = {})
951
- scope(:defaults => defaults) { yield }
1003
+ @scope = @scope.new(defaults: merge_defaults_scope(@scope[:defaults], defaults))
1004
+ yield
1005
+ ensure
1006
+ @scope = @scope.parent
952
1007
  end
953
1008
 
954
1009
  private
@@ -980,6 +1035,14 @@ module ActionDispatch
980
1035
  child
981
1036
  end
982
1037
 
1038
+ def merge_via_scope(parent, child) #:nodoc:
1039
+ child
1040
+ end
1041
+
1042
+ def merge_format_scope(parent, child) #:nodoc:
1043
+ child
1044
+ end
1045
+
983
1046
  def merge_path_names_scope(parent, child) #:nodoc:
984
1047
  merge_options_scope(parent, child)
985
1048
  end
@@ -999,15 +1062,15 @@ module ActionDispatch
999
1062
  end
1000
1063
 
1001
1064
  def merge_options_scope(parent, child) #:nodoc:
1002
- (parent || {}).except(*override_keys(child)).merge!(child)
1065
+ (parent || {}).merge(child)
1003
1066
  end
1004
1067
 
1005
1068
  def merge_shallow_scope(parent, child) #:nodoc:
1006
1069
  child ? true : false
1007
1070
  end
1008
1071
 
1009
- def override_keys(child) #:nodoc:
1010
- child.key?(:only) || child.key?(:except) ? [:only, :except] : []
1072
+ def merge_to_scope(parent, child)
1073
+ child
1011
1074
  end
1012
1075
  end
1013
1076
 
@@ -1058,27 +1121,34 @@ module ActionDispatch
1058
1121
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1059
1122
 
1060
1123
  class Resource #:nodoc:
1061
- attr_reader :controller, :path, :options, :param
1124
+ attr_reader :controller, :path, :param
1062
1125
 
1063
- def initialize(entities, options = {})
1126
+ def initialize(entities, api_only, shallow, options = {})
1064
1127
  @name = entities.to_s
1065
1128
  @path = (options[:path] || @name).to_s
1066
1129
  @controller = (options[:controller] || @name).to_s
1067
1130
  @as = options[:as]
1068
1131
  @param = (options[:param] || :id).to_sym
1069
1132
  @options = options
1070
- @shallow = false
1133
+ @shallow = shallow
1134
+ @api_only = api_only
1135
+ @only = options.delete :only
1136
+ @except = options.delete :except
1071
1137
  end
1072
1138
 
1073
1139
  def default_actions
1074
- [:index, :create, :new, :show, :update, :destroy, :edit]
1140
+ if @api_only
1141
+ [:index, :create, :show, :update, :destroy]
1142
+ else
1143
+ [:index, :create, :new, :show, :update, :destroy, :edit]
1144
+ end
1075
1145
  end
1076
1146
 
1077
1147
  def actions
1078
- if only = @options[:only]
1079
- Array(only).map(&:to_sym)
1080
- elsif except = @options[:except]
1081
- default_actions - Array(except).map(&:to_sym)
1148
+ if @only
1149
+ Array(@only).map(&:to_sym)
1150
+ elsif @except
1151
+ default_actions - Array(@except).map(&:to_sym)
1082
1152
  else
1083
1153
  default_actions
1084
1154
  end
@@ -1105,7 +1175,7 @@ module ActionDispatch
1105
1175
  end
1106
1176
 
1107
1177
  def resource_scope
1108
- { :controller => controller }
1178
+ controller
1109
1179
  end
1110
1180
 
1111
1181
  alias :collection_scope :path
@@ -1128,17 +1198,15 @@ module ActionDispatch
1128
1198
  "#{path}/:#{nested_param}"
1129
1199
  end
1130
1200
 
1131
- def shallow=(value)
1132
- @shallow = value
1133
- end
1134
-
1135
1201
  def shallow?
1136
1202
  @shallow
1137
1203
  end
1204
+
1205
+ def singleton?; false; end
1138
1206
  end
1139
1207
 
1140
1208
  class SingletonResource < Resource #:nodoc:
1141
- def initialize(entities, options)
1209
+ def initialize(entities, api_only, shallow, options)
1142
1210
  super
1143
1211
  @as = nil
1144
1212
  @controller = (options[:controller] || plural).to_s
@@ -1146,7 +1214,11 @@ module ActionDispatch
1146
1214
  end
1147
1215
 
1148
1216
  def default_actions
1149
- [:show, :create, :update, :destroy, :new, :edit]
1217
+ if @api_only
1218
+ [:show, :create, :update, :destroy]
1219
+ else
1220
+ [:show, :create, :update, :destroy, :new, :edit]
1221
+ end
1150
1222
  end
1151
1223
 
1152
1224
  def plural
@@ -1162,6 +1234,8 @@ module ActionDispatch
1162
1234
 
1163
1235
  alias :member_scope :path
1164
1236
  alias :nested_scope :path
1237
+
1238
+ def singleton?; true; end
1165
1239
  end
1166
1240
 
1167
1241
  def resources_path_names(options)
@@ -1181,11 +1255,11 @@ module ActionDispatch
1181
1255
  # the plural):
1182
1256
  #
1183
1257
  # GET /profile/new
1184
- # POST /profile
1185
1258
  # GET /profile
1186
1259
  # GET /profile/edit
1187
1260
  # PATCH/PUT /profile
1188
1261
  # DELETE /profile
1262
+ # POST /profile
1189
1263
  #
1190
1264
  # === Options
1191
1265
  # Takes same options as +resources+.
@@ -1196,20 +1270,23 @@ module ActionDispatch
1196
1270
  return self
1197
1271
  end
1198
1272
 
1199
- resource_scope(:resource, SingletonResource.new(resources.pop, options)) do
1200
- yield if block_given?
1273
+ with_scope_level(:resource) do
1274
+ options = apply_action_options options
1275
+ resource_scope(SingletonResource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1276
+ yield if block_given?
1201
1277
 
1202
- concerns(options[:concerns]) if options[:concerns]
1278
+ concerns(options[:concerns]) if options[:concerns]
1203
1279
 
1204
- collection do
1205
- post :create
1206
- end if parent_resource.actions.include?(:create)
1280
+ new do
1281
+ get :new
1282
+ end if parent_resource.actions.include?(:new)
1207
1283
 
1208
- new do
1209
- get :new
1210
- end if parent_resource.actions.include?(:new)
1284
+ set_member_mappings_for_resource
1211
1285
 
1212
- set_member_mappings_for_resource
1286
+ collection do
1287
+ post :create
1288
+ end if parent_resource.actions.include?(:create)
1289
+ end
1213
1290
  end
1214
1291
 
1215
1292
  self
@@ -1354,21 +1431,24 @@ module ActionDispatch
1354
1431
  return self
1355
1432
  end
1356
1433
 
1357
- resource_scope(:resources, Resource.new(resources.pop, options)) do
1358
- yield if block_given?
1434
+ with_scope_level(:resources) do
1435
+ options = apply_action_options options
1436
+ resource_scope(Resource.new(resources.pop, api_only?, @scope[:shallow], options)) do
1437
+ yield if block_given?
1359
1438
 
1360
- concerns(options[:concerns]) if options[:concerns]
1439
+ concerns(options[:concerns]) if options[:concerns]
1361
1440
 
1362
- collection do
1363
- get :index if parent_resource.actions.include?(:index)
1364
- post :create if parent_resource.actions.include?(:create)
1365
- end
1441
+ collection do
1442
+ get :index if parent_resource.actions.include?(:index)
1443
+ post :create if parent_resource.actions.include?(:create)
1444
+ end
1366
1445
 
1367
- new do
1368
- get :new
1369
- end if parent_resource.actions.include?(:new)
1446
+ new do
1447
+ get :new
1448
+ end if parent_resource.actions.include?(:new)
1370
1449
 
1371
- set_member_mappings_for_resource
1450
+ set_member_mappings_for_resource
1451
+ end
1372
1452
  end
1373
1453
 
1374
1454
  self
@@ -1392,7 +1472,7 @@ module ActionDispatch
1392
1472
  end
1393
1473
 
1394
1474
  with_scope_level(:collection) do
1395
- scope(parent_resource.collection_scope) do
1475
+ path_scope(parent_resource.collection_scope) do
1396
1476
  yield
1397
1477
  end
1398
1478
  end
@@ -1416,9 +1496,11 @@ module ActionDispatch
1416
1496
 
1417
1497
  with_scope_level(:member) do
1418
1498
  if shallow?
1419
- shallow_scope(parent_resource.member_scope) { yield }
1499
+ shallow_scope {
1500
+ path_scope(parent_resource.member_scope) { yield }
1501
+ }
1420
1502
  else
1421
- scope(parent_resource.member_scope) { yield }
1503
+ path_scope(parent_resource.member_scope) { yield }
1422
1504
  end
1423
1505
  end
1424
1506
  end
@@ -1429,7 +1511,7 @@ module ActionDispatch
1429
1511
  end
1430
1512
 
1431
1513
  with_scope_level(:new) do
1432
- scope(parent_resource.new_scope(action_path(:new))) do
1514
+ path_scope(parent_resource.new_scope(action_path(:new))) do
1433
1515
  yield
1434
1516
  end
1435
1517
  end
@@ -1442,9 +1524,15 @@ module ActionDispatch
1442
1524
 
1443
1525
  with_scope_level(:nested) do
1444
1526
  if shallow? && shallow_nesting_depth >= 1
1445
- shallow_scope(parent_resource.nested_scope, nested_options) { yield }
1527
+ shallow_scope do
1528
+ path_scope(parent_resource.nested_scope) do
1529
+ scope(nested_options) { yield }
1530
+ end
1531
+ end
1446
1532
  else
1447
- scope(parent_resource.nested_scope, nested_options) { yield }
1533
+ path_scope(parent_resource.nested_scope) do
1534
+ scope(nested_options) { yield }
1535
+ end
1448
1536
  end
1449
1537
  end
1450
1538
  end
@@ -1459,23 +1547,33 @@ module ActionDispatch
1459
1547
  end
1460
1548
 
1461
1549
  def shallow
1462
- scope(:shallow => true) do
1463
- yield
1464
- end
1550
+ @scope = @scope.new(shallow: true)
1551
+ yield
1552
+ ensure
1553
+ @scope = @scope.parent
1465
1554
  end
1466
1555
 
1467
1556
  def shallow?
1468
- parent_resource.instance_of?(Resource) && @scope[:shallow]
1557
+ !parent_resource.singleton? && @scope[:shallow]
1469
1558
  end
1470
1559
 
1471
- # match 'path' => 'controller#action'
1472
- # match 'path', to: 'controller#action'
1473
- # match 'path', 'otherpath', on: :member, via: :get
1474
- def match(path, *rest)
1560
+ # Matches a url pattern to one or more routes.
1561
+ # For more information, see match[rdoc-ref:Base#match].
1562
+ #
1563
+ # match 'path' => 'controller#action', via: patch
1564
+ # match 'path', to: 'controller#action', via: :post
1565
+ # match 'path', 'otherpath', on: :member, via: :get
1566
+ def match(path, *rest, &block)
1475
1567
  if rest.empty? && Hash === path
1476
1568
  options = path
1477
1569
  path, to = options.find { |name, _value| name.is_a?(String) }
1478
1570
 
1571
+ if path.nil?
1572
+ ActiveSupport::Deprecation.warn 'Omitting the route path is deprecated. '\
1573
+ 'Specify the path with a String or a Symbol instead.'
1574
+ path = ''
1575
+ end
1576
+
1479
1577
  case to
1480
1578
  when Symbol
1481
1579
  options[:action] = to
@@ -1496,58 +1594,53 @@ module ActionDispatch
1496
1594
  paths = [path] + rest
1497
1595
  end
1498
1596
 
1499
- options[:anchor] = true unless options.key?(:anchor)
1500
-
1501
- if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1502
- raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1503
- end
1504
-
1505
- if @scope[:controller] && @scope[:action]
1506
- options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1597
+ if options.key?(:defaults)
1598
+ defaults(options.delete(:defaults)) { map_match(paths, options, &block) }
1599
+ else
1600
+ map_match(paths, options, &block)
1507
1601
  end
1602
+ end
1508
1603
 
1509
- paths.each do |_path|
1510
- route_options = options.dup
1511
- route_options[:path] ||= _path if _path.is_a?(String)
1512
-
1513
- path_without_format = _path.to_s.sub(/\(\.:format\)$/, '')
1514
- if using_match_shorthand?(path_without_format, route_options)
1515
- route_options[:to] ||= path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1')
1516
- route_options[:to].tr!("-", "_")
1517
- end
1604
+ def get_to_from_path(path, to, action)
1605
+ return to if to || action
1518
1606
 
1519
- decomposed_match(_path, route_options)
1607
+ path_without_format = path.sub(/\(\.:format\)$/, '')
1608
+ if using_match_shorthand?(path_without_format)
1609
+ path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1610
+ else
1611
+ nil
1520
1612
  end
1521
- self
1522
1613
  end
1523
1614
 
1524
- def using_match_shorthand?(path, options)
1525
- path && (options[:to] || options[:action]).nil? && path =~ %r{^/?[-\w]+/[-\w/]+$}
1615
+ def using_match_shorthand?(path)
1616
+ path =~ %r{^/?[-\w]+/[-\w/]+$}
1526
1617
  end
1527
1618
 
1528
- def decomposed_match(path, options) # :nodoc:
1619
+ def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
1529
1620
  if on = options.delete(:on)
1530
- send(on) { decomposed_match(path, options) }
1621
+ send(on) { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1531
1622
  else
1532
1623
  case @scope.scope_level
1533
1624
  when :resources
1534
- nested { decomposed_match(path, options) }
1625
+ nested { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1535
1626
  when :resource
1536
- member { decomposed_match(path, options) }
1627
+ member { decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints) }
1537
1628
  else
1538
- add_route(path, options)
1629
+ add_route(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
1539
1630
  end
1540
1631
  end
1541
1632
  end
1542
1633
 
1543
- def add_route(action, options) # :nodoc:
1544
- path = path_for_action(action, options.delete(:path))
1634
+ def add_route(action, controller, options, _path, to, via, formatted, anchor, options_constraints) # :nodoc:
1635
+ path = path_for_action(action, _path)
1545
1636
  raise ArgumentError, "path is required" if path.blank?
1546
1637
 
1547
- action = action.to_s.dup
1638
+ action = action.to_s
1639
+
1640
+ default_action = options.delete(:action) || @scope[:action]
1548
1641
 
1549
1642
  if action =~ /^[\w\-\/]+$/
1550
- options[:action] ||= action.tr('-', '_') unless action.include?("/")
1643
+ default_action ||= action.tr('-', '_') unless action.include?("/")
1551
1644
  else
1552
1645
  action = nil
1553
1646
  end
@@ -1558,12 +1651,27 @@ module ActionDispatch
1558
1651
  name_for_action(options.delete(:as), action)
1559
1652
  end
1560
1653
 
1561
- mapping = Mapping.build(@scope, @set, URI.parser.escape(path), as, options)
1562
- app, conditions, requirements, defaults, as, anchor = mapping.to_route
1563
- @set.add_route(app, conditions, requirements, defaults, as, anchor)
1654
+ path = Mapping.normalize_path URI.parser.escape(path), formatted
1655
+ ast = Journey::Parser.parse path
1656
+
1657
+ mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
1658
+ @set.add_route(mapping, ast, as, anchor)
1564
1659
  end
1565
1660
 
1566
- def root(path, options={})
1661
+ # You can specify what Rails should route "/" to with the root method:
1662
+ #
1663
+ # root to: 'pages#main'
1664
+ #
1665
+ # For options, see +match+, as +root+ uses it internally.
1666
+ #
1667
+ # You can also pass a string which will expand
1668
+ #
1669
+ # root 'pages#main'
1670
+ #
1671
+ # You should put the root route at the top of <tt>config/routes.rb</tt>,
1672
+ # because this means it will be matched first. As this is the most popular route
1673
+ # of most Rails applications, this is beneficial.
1674
+ def root(path, options = {})
1567
1675
  if path.is_a?(String)
1568
1676
  options[:to] = path
1569
1677
  elsif path.is_a?(Hash) and options.empty?
@@ -1574,12 +1682,12 @@ module ActionDispatch
1574
1682
 
1575
1683
  if @scope.resources?
1576
1684
  with_scope_level(:root) do
1577
- scope(parent_resource.path) do
1578
- super(options)
1685
+ path_scope(parent_resource.path) do
1686
+ match_root_route(options)
1579
1687
  end
1580
1688
  end
1581
1689
  else
1582
- super(options)
1690
+ match_root_route(options)
1583
1691
  end
1584
1692
  end
1585
1693
 
@@ -1619,23 +1727,20 @@ module ActionDispatch
1619
1727
  return true
1620
1728
  end
1621
1729
 
1622
- unless action_options?(options)
1623
- options.merge!(scope_action_options) if scope_action_options?
1624
- end
1625
-
1626
1730
  false
1627
1731
  end
1628
1732
 
1629
- def action_options?(options) #:nodoc:
1630
- options[:only] || options[:except]
1733
+ def apply_action_options(options) # :nodoc:
1734
+ return options if action_options? options
1735
+ options.merge scope_action_options
1631
1736
  end
1632
1737
 
1633
- def scope_action_options? #:nodoc:
1634
- @scope[:options] && (@scope[:options][:only] || @scope[:options][:except])
1738
+ def action_options?(options) #:nodoc:
1739
+ options[:only] || options[:except]
1635
1740
  end
1636
1741
 
1637
1742
  def scope_action_options #:nodoc:
1638
- @scope[:options].slice(:only, :except)
1743
+ @scope[:action_options] || {}
1639
1744
  end
1640
1745
 
1641
1746
  def resource_scope? #:nodoc:
@@ -1650,18 +1755,6 @@ module ActionDispatch
1650
1755
  @scope.nested?
1651
1756
  end
1652
1757
 
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
1758
  def with_scope_level(kind)
1666
1759
  @scope = @scope.new_level(kind)
1667
1760
  yield
@@ -1669,16 +1762,11 @@ module ActionDispatch
1669
1762
  @scope = @scope.parent
1670
1763
  end
1671
1764
 
1672
- def resource_scope(kind, resource) #:nodoc:
1673
- resource.shallow = @scope[:shallow]
1765
+ def resource_scope(resource) #:nodoc:
1674
1766
  @scope = @scope.new(:scope_level_resource => resource)
1675
- @nesting.push(resource)
1676
1767
 
1677
- with_scope_level(kind) do
1678
- scope(parent_resource.resource_scope) { yield }
1679
- end
1768
+ controller(resource.resource_scope) { yield }
1680
1769
  ensure
1681
- @nesting.pop
1682
1770
  @scope = @scope.parent
1683
1771
  end
1684
1772
 
@@ -1691,12 +1779,10 @@ module ActionDispatch
1691
1779
  options
1692
1780
  end
1693
1781
 
1694
- def nesting_depth #:nodoc:
1695
- @nesting.size
1696
- end
1697
-
1698
1782
  def shallow_nesting_depth #:nodoc:
1699
- @nesting.select(&:shallow?).size
1783
+ @scope.find_all { |node|
1784
+ node.frame[:scope_level_resource]
1785
+ }.count { |node| node.frame[:scope_level_resource].shallow? }
1700
1786
  end
1701
1787
 
1702
1788
  def param_constraint? #:nodoc:
@@ -1711,27 +1797,28 @@ module ActionDispatch
1711
1797
  resource_method_scope? && CANONICAL_ACTIONS.include?(action.to_s)
1712
1798
  end
1713
1799
 
1714
- def shallow_scope(path, options = {}) #:nodoc:
1800
+ def shallow_scope #:nodoc:
1715
1801
  scope = { :as => @scope[:shallow_prefix],
1716
1802
  :path => @scope[:shallow_path] }
1717
1803
  @scope = @scope.new scope
1718
1804
 
1719
- scope(path, options) { yield }
1805
+ yield
1720
1806
  ensure
1721
1807
  @scope = @scope.parent
1722
1808
  end
1723
1809
 
1724
1810
  def path_for_action(action, path) #:nodoc:
1725
- if path.blank? && canonical_action?(action)
1811
+ return "#{@scope[:path]}/#{path}" if path
1812
+
1813
+ if canonical_action?(action)
1726
1814
  @scope[:path].to_s
1727
1815
  else
1728
- "#{@scope[:path]}/#{action_path(action, path)}"
1816
+ "#{@scope[:path]}/#{action_path(action)}"
1729
1817
  end
1730
1818
  end
1731
1819
 
1732
- def action_path(name, path = nil) #:nodoc:
1733
- name = name.to_sym if name.is_a?(String)
1734
- path || @scope[:path_names][name] || name.to_s
1820
+ def action_path(name) #:nodoc:
1821
+ @scope[:path_names][name.to_sym] || name
1735
1822
  end
1736
1823
 
1737
1824
  def prefix_name_for_action(as, action) #:nodoc:
@@ -1757,14 +1844,15 @@ module ActionDispatch
1757
1844
  member_name = parent_resource.member_name
1758
1845
  end
1759
1846
 
1760
- name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
1847
+ action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name)
1848
+ candidate = action_name.select(&:present?).join('_')
1761
1849
 
1762
- if candidate = name.compact.join("_").presence
1850
+ unless candidate.empty?
1763
1851
  # If a name was not explicitly given, we check if it is valid
1764
1852
  # and return nil in case it isn't. Otherwise, we pass the invalid name
1765
1853
  # forward so the underlying router engine treats it and raises an exception.
1766
1854
  if as.nil?
1767
- candidate unless candidate !~ /\A[_a-z]/i || @set.named_routes.key?(candidate)
1855
+ candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
1768
1856
  else
1769
1857
  candidate
1770
1858
  end
@@ -1782,6 +1870,78 @@ module ActionDispatch
1782
1870
  delete :destroy if parent_resource.actions.include?(:destroy)
1783
1871
  end
1784
1872
  end
1873
+
1874
+ def api_only?
1875
+ @set.api_only?
1876
+ end
1877
+
1878
+ private
1879
+
1880
+ def path_scope(path)
1881
+ @scope = @scope.new(path: merge_path_scope(@scope[:path], path))
1882
+ yield
1883
+ ensure
1884
+ @scope = @scope.parent
1885
+ end
1886
+
1887
+ def map_match(paths, options)
1888
+ if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1889
+ raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1890
+ end
1891
+
1892
+ if @scope[:to]
1893
+ options[:to] ||= @scope[:to]
1894
+ end
1895
+
1896
+ if @scope[:controller] && @scope[:action]
1897
+ options[:to] ||= "#{@scope[:controller]}##{@scope[:action]}"
1898
+ end
1899
+
1900
+ controller = options.delete(:controller) || @scope[:controller]
1901
+ option_path = options.delete :path
1902
+ to = options.delete :to
1903
+ via = Mapping.check_via Array(options.delete(:via) {
1904
+ @scope[:via]
1905
+ })
1906
+ formatted = options.delete(:format) { @scope[:format] }
1907
+ anchor = options.delete(:anchor) { true }
1908
+ options_constraints = options.delete(:constraints) || {}
1909
+
1910
+ path_types = paths.group_by(&:class)
1911
+ path_types.fetch(String, []).each do |_path|
1912
+ route_options = options.dup
1913
+ if _path && option_path
1914
+ ActiveSupport::Deprecation.warn <<-eowarn
1915
+ Specifying strings for both :path and the route path is deprecated. Change things like this:
1916
+
1917
+ match #{_path.inspect}, :path => #{option_path.inspect}
1918
+
1919
+ to this:
1920
+
1921
+ match #{option_path.inspect}, :as => #{_path.inspect}, :action => #{_path.inspect}
1922
+ eowarn
1923
+ route_options[:action] = _path
1924
+ route_options[:as] = _path
1925
+ _path = option_path
1926
+ end
1927
+ to = get_to_from_path(_path, to, route_options[:action])
1928
+ decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1929
+ end
1930
+
1931
+ path_types.fetch(Symbol, []).each do |action|
1932
+ route_options = options.dup
1933
+ decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1934
+ end
1935
+
1936
+ self
1937
+ end
1938
+
1939
+ def match_root_route(options)
1940
+ name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
1941
+ args = ["/", { as: name, via: :get }.merge!(options)]
1942
+
1943
+ match(*args)
1944
+ end
1785
1945
  end
1786
1946
 
1787
1947
  # Routing Concerns allow you to declare common routes that can be reused
@@ -1892,14 +2052,14 @@ module ActionDispatch
1892
2052
  class Scope # :nodoc:
1893
2053
  OPTIONS = [:path, :shallow_path, :as, :shallow_prefix, :module,
1894
2054
  :controller, :action, :path_names, :constraints,
1895
- :shallow, :blocks, :defaults, :options]
2055
+ :shallow, :blocks, :defaults, :via, :format, :options, :to]
1896
2056
 
1897
2057
  RESOURCE_SCOPES = [:resource, :resources]
1898
2058
  RESOURCE_METHOD_SCOPES = [:collection, :member, :new]
1899
2059
 
1900
2060
  attr_reader :parent, :scope_level
1901
2061
 
1902
- def initialize(hash, parent = {}, scope_level = nil)
2062
+ def initialize(hash, parent = NULL, scope_level = nil)
1903
2063
  @hash = hash
1904
2064
  @parent = parent
1905
2065
  @scope_level = scope_level
@@ -1947,27 +2107,34 @@ module ActionDispatch
1947
2107
  end
1948
2108
 
1949
2109
  def new_level(level)
1950
- self.class.new(self, self, level)
1951
- end
1952
-
1953
- def fetch(key, &block)
1954
- @hash.fetch(key, &block)
2110
+ self.class.new(frame, self, level)
1955
2111
  end
1956
2112
 
1957
2113
  def [](key)
1958
- @hash.fetch(key) { @parent[key] }
2114
+ scope = find { |node| node.frame.key? key }
2115
+ scope && scope.frame[key]
1959
2116
  end
1960
2117
 
1961
- def []=(k,v)
1962
- @hash[k] = v
2118
+ include Enumerable
2119
+
2120
+ def each
2121
+ node = self
2122
+ loop do
2123
+ break if node.equal? NULL
2124
+ yield node
2125
+ node = node.parent
2126
+ end
1963
2127
  end
2128
+
2129
+ def frame; @hash; end
2130
+
2131
+ NULL = Scope.new(nil, nil)
1964
2132
  end
1965
2133
 
1966
2134
  def initialize(set) #:nodoc:
1967
2135
  @set = set
1968
2136
  @scope = Scope.new({ :path_names => @set.resources_path_names })
1969
2137
  @concerns = {}
1970
- @nesting = []
1971
2138
  end
1972
2139
 
1973
2140
  include Base