actionpack 4.2.10 → 5.0.0

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