actionpack 5.2.0 → 6.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (156) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +408 -190
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +4 -3
  5. data/lib/abstract_controller/base.rb +38 -4
  6. data/lib/abstract_controller/caching/fragments.rb +6 -22
  7. data/lib/abstract_controller/caching.rb +1 -1
  8. data/lib/abstract_controller/callbacks.rb +14 -2
  9. data/lib/abstract_controller/collector.rb +1 -2
  10. data/lib/abstract_controller/helpers.rb +106 -90
  11. data/lib/abstract_controller/railties/routes_helpers.rb +17 -1
  12. data/lib/abstract_controller/rendering.rb +9 -9
  13. data/lib/abstract_controller/translation.rb +11 -5
  14. data/lib/abstract_controller.rb +2 -0
  15. data/lib/action_controller/api.rb +4 -3
  16. data/lib/action_controller/base.rb +6 -9
  17. data/lib/action_controller/caching.rb +1 -3
  18. data/lib/action_controller/log_subscriber.rb +10 -7
  19. data/lib/action_controller/metal/basic_implicit_render.rb +1 -1
  20. data/lib/action_controller/metal/conditional_get.rb +19 -5
  21. data/lib/action_controller/metal/content_security_policy.rb +1 -2
  22. data/lib/action_controller/metal/cookies.rb +3 -1
  23. data/lib/action_controller/metal/data_streaming.rb +6 -7
  24. data/lib/action_controller/metal/default_headers.rb +17 -0
  25. data/lib/action_controller/metal/etag_with_template_digest.rb +4 -6
  26. data/lib/action_controller/metal/exceptions.rb +56 -2
  27. data/lib/action_controller/metal/flash.rb +5 -5
  28. data/lib/action_controller/metal/head.rb +7 -4
  29. data/lib/action_controller/metal/helpers.rb +14 -5
  30. data/lib/action_controller/metal/http_authentication.rb +25 -24
  31. data/lib/action_controller/metal/implicit_render.rb +5 -15
  32. data/lib/action_controller/metal/instrumentation.rb +13 -14
  33. data/lib/action_controller/metal/live.rb +39 -32
  34. data/lib/action_controller/metal/logging.rb +20 -0
  35. data/lib/action_controller/metal/mime_responds.rb +19 -4
  36. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  37. data/lib/action_controller/metal/params_wrapper.rb +33 -23
  38. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  39. data/lib/action_controller/metal/redirecting.rb +7 -7
  40. data/lib/action_controller/metal/renderers.rb +4 -4
  41. data/lib/action_controller/metal/rendering.rb +8 -3
  42. data/lib/action_controller/metal/request_forgery_protection.rb +89 -36
  43. data/lib/action_controller/metal/rescue.rb +1 -1
  44. data/lib/action_controller/metal/streaming.rb +0 -1
  45. data/lib/action_controller/metal/strong_parameters.rb +181 -69
  46. data/lib/action_controller/metal/url_for.rb +1 -1
  47. data/lib/action_controller/metal.rb +12 -10
  48. data/lib/action_controller/railties/helpers.rb +1 -1
  49. data/lib/action_controller/renderer.rb +37 -13
  50. data/lib/action_controller/template_assertions.rb +1 -1
  51. data/lib/action_controller/test_case.rb +81 -70
  52. data/lib/action_controller.rb +7 -4
  53. data/lib/action_dispatch/http/cache.rb +34 -28
  54. data/lib/action_dispatch/http/content_disposition.rb +45 -0
  55. data/lib/action_dispatch/http/content_security_policy.rb +47 -24
  56. data/lib/action_dispatch/http/filter_parameters.rb +9 -8
  57. data/lib/action_dispatch/http/filter_redirect.rb +2 -3
  58. data/lib/action_dispatch/http/headers.rb +4 -4
  59. data/lib/action_dispatch/http/mime_negotiation.rb +31 -13
  60. data/lib/action_dispatch/http/mime_type.rb +43 -24
  61. data/lib/action_dispatch/http/parameters.rb +14 -23
  62. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  63. data/lib/action_dispatch/http/request.rb +45 -22
  64. data/lib/action_dispatch/http/response.rb +45 -25
  65. data/lib/action_dispatch/http/upload.rb +9 -1
  66. data/lib/action_dispatch/http/url.rb +82 -82
  67. data/lib/action_dispatch/journey/formatter.rb +55 -31
  68. data/lib/action_dispatch/journey/gtg/builder.rb +22 -37
  69. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  70. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -5
  71. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  72. data/lib/action_dispatch/journey/nodes/node.rb +13 -11
  73. data/lib/action_dispatch/journey/parser.rb +13 -13
  74. data/lib/action_dispatch/journey/parser.y +1 -1
  75. data/lib/action_dispatch/journey/path/pattern.rb +21 -22
  76. data/lib/action_dispatch/journey/route.rb +10 -20
  77. data/lib/action_dispatch/journey/router/utils.rb +14 -12
  78. data/lib/action_dispatch/journey/router.rb +26 -34
  79. data/lib/action_dispatch/journey/routes.rb +1 -2
  80. data/lib/action_dispatch/journey/scanner.rb +10 -4
  81. data/lib/action_dispatch/journey/visitors.rb +1 -4
  82. data/lib/action_dispatch/journey.rb +0 -2
  83. data/lib/action_dispatch/middleware/actionable_exceptions.rb +46 -0
  84. data/lib/action_dispatch/middleware/callbacks.rb +2 -4
  85. data/lib/action_dispatch/middleware/cookies.rb +128 -109
  86. data/lib/action_dispatch/middleware/debug_exceptions.rb +43 -66
  87. data/lib/action_dispatch/middleware/debug_locks.rb +5 -5
  88. data/lib/action_dispatch/middleware/debug_view.rb +66 -0
  89. data/lib/action_dispatch/middleware/exception_wrapper.rb +75 -30
  90. data/lib/action_dispatch/middleware/flash.rb +2 -2
  91. data/lib/action_dispatch/middleware/host_authorization.rb +130 -0
  92. data/lib/action_dispatch/middleware/public_exceptions.rb +6 -3
  93. data/lib/action_dispatch/middleware/remote_ip.rb +14 -16
  94. data/lib/action_dispatch/middleware/request_id.rb +5 -6
  95. data/lib/action_dispatch/middleware/session/abstract_store.rb +15 -2
  96. data/lib/action_dispatch/middleware/session/cache_store.rb +11 -6
  97. data/lib/action_dispatch/middleware/session/cookie_store.rb +24 -19
  98. data/lib/action_dispatch/middleware/show_exceptions.rb +3 -2
  99. data/lib/action_dispatch/middleware/ssl.rb +20 -15
  100. data/lib/action_dispatch/middleware/stack.rb +57 -3
  101. data/lib/action_dispatch/middleware/static.rb +153 -93
  102. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +13 -0
  103. data/lib/action_dispatch/middleware/templates/rescues/_actions.text.erb +0 -0
  104. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  105. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +3 -1
  106. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb +1 -1
  107. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +4 -2
  108. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +45 -35
  109. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +7 -0
  110. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +5 -0
  111. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +23 -4
  112. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +1 -1
  113. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +6 -3
  114. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +4 -1
  115. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +104 -8
  116. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +19 -0
  117. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.text.erb +3 -0
  118. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +2 -2
  119. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +1 -1
  120. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +2 -2
  121. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  122. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +24 -1
  123. data/lib/action_dispatch/railtie.rb +8 -2
  124. data/lib/action_dispatch/request/session.rb +17 -10
  125. data/lib/action_dispatch/request/utils.rb +28 -2
  126. data/lib/action_dispatch/routing/inspector.rb +101 -53
  127. data/lib/action_dispatch/routing/mapper.rb +156 -103
  128. data/lib/action_dispatch/routing/polymorphic_routes.rb +21 -19
  129. data/lib/action_dispatch/routing/redirection.rb +4 -4
  130. data/lib/action_dispatch/routing/route_set.rb +71 -69
  131. data/lib/action_dispatch/routing/url_for.rb +3 -3
  132. data/lib/action_dispatch/routing.rb +21 -20
  133. data/lib/action_dispatch/system_test_case.rb +54 -11
  134. data/lib/action_dispatch/system_testing/browser.rb +53 -16
  135. data/lib/action_dispatch/system_testing/driver.rb +11 -3
  136. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +49 -7
  137. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +8 -6
  138. data/lib/action_dispatch/testing/assertion_response.rb +0 -1
  139. data/lib/action_dispatch/testing/assertions/response.rb +4 -7
  140. data/lib/action_dispatch/testing/assertions/routing.rb +20 -8
  141. data/lib/action_dispatch/testing/assertions.rb +1 -1
  142. data/lib/action_dispatch/testing/integration.rb +61 -28
  143. data/lib/action_dispatch/testing/request_encoder.rb +3 -3
  144. data/lib/action_dispatch/testing/test_process.rb +29 -4
  145. data/lib/action_dispatch/testing/test_request.rb +3 -3
  146. data/lib/action_dispatch/testing/test_response.rb +4 -32
  147. data/lib/action_dispatch.rb +14 -7
  148. data/lib/action_pack/gem_version.rb +3 -3
  149. data/lib/action_pack.rb +1 -1
  150. metadata +39 -22
  151. data/lib/action_controller/metal/force_ssl.rb +0 -99
  152. data/lib/action_dispatch/http/parameter_filter.rb +0 -86
  153. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  154. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -49
  155. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -120
  156. data/lib/action_dispatch/system_testing/test_helpers/undef_methods.rb +0 -26
@@ -4,6 +4,7 @@ require "active_support/core_ext/hash/slice"
4
4
  require "active_support/core_ext/enumerable"
5
5
  require "active_support/core_ext/array/extract_options"
6
6
  require "active_support/core_ext/regexp"
7
+ require "active_support/core_ext/symbol/starts_ends_with"
7
8
  require "action_dispatch/routing/redirection"
8
9
  require "action_dispatch/routing/endpoint"
9
10
 
@@ -50,7 +51,19 @@ module ActionDispatch
50
51
 
51
52
  private
52
53
  def constraint_args(constraint, request)
53
- constraint.arity == 1 ? [request] : [request.path_parameters, request]
54
+ arity = if constraint.respond_to?(:arity)
55
+ constraint.arity
56
+ else
57
+ constraint.method(:call).arity
58
+ end
59
+
60
+ if arity < 1
61
+ []
62
+ elsif arity == 1
63
+ [request]
64
+ else
65
+ [request.path_parameters, request]
66
+ end
54
67
  end
55
68
  end
56
69
 
@@ -58,17 +71,21 @@ module ActionDispatch
58
71
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
59
72
  OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
60
73
 
61
- attr_reader :requirements, :defaults
62
- attr_reader :to, :default_controller, :default_action
63
- attr_reader :required_defaults, :ast
74
+ attr_reader :requirements, :defaults, :to, :default_controller,
75
+ :default_action, :required_defaults, :ast, :scope_options
64
76
 
65
77
  def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
66
- options = scope[:options].merge(options) if scope[:options]
67
-
68
- defaults = (scope[:defaults] || {}).dup
69
- scope_constraints = scope[:constraints] || {}
78
+ scope_params = {
79
+ blocks: scope[:blocks] || [],
80
+ constraints: scope[:constraints] || {},
81
+ defaults: (scope[:defaults] || {}).dup,
82
+ module: scope[:module],
83
+ options: scope[:options] || {}
84
+ }
70
85
 
71
- new set, ast, defaults, controller, default_action, scope[:module], to, formatted, scope_constraints, scope[:blocks] || [], via, options_constraints, anchor, options
86
+ new set: set, ast: ast, controller: controller, default_action: default_action,
87
+ to: to, formatted: formatted, via: via, options_constraints: options_constraints,
88
+ anchor: anchor, scope_params: scope_params, options: scope_params[:options].merge(options)
72
89
  end
73
90
 
74
91
  def self.check_via(via)
@@ -96,36 +113,48 @@ module ActionDispatch
96
113
  end
97
114
 
98
115
  def self.optional_format?(path, format)
99
- format != false && path !~ OPTIONAL_FORMAT_REGEX
116
+ format != false && !path.match?(OPTIONAL_FORMAT_REGEX)
100
117
  end
101
118
 
102
- def initialize(set, ast, defaults, controller, default_action, modyoule, to, formatted, scope_constraints, blocks, via, options_constraints, anchor, options)
103
- @defaults = defaults
104
- @set = set
105
-
106
- @to = to
107
- @default_controller = controller
108
- @default_action = default_action
119
+ def initialize(set:, ast:, controller:, default_action:, to:, formatted:, via:, options_constraints:, anchor:, scope_params:, options:)
120
+ @defaults = scope_params[:defaults]
121
+ @set = set
122
+ @to = intern(to)
123
+ @default_controller = intern(controller)
124
+ @default_action = intern(default_action)
109
125
  @ast = ast
110
126
  @anchor = anchor
111
127
  @via = via
112
128
  @internal = options.delete(:internal)
129
+ @scope_options = scope_params[:options]
130
+
131
+ path_params = []
132
+ wildcard_options = {}
133
+ ast.each do |node|
134
+ if node.symbol?
135
+ path_params << node.to_sym
136
+ elsif formatted != false && node.star?
137
+ # Add a constraint for wildcard route to make it non-greedy and match the
138
+ # optional format part of the route by default.
139
+ wildcard_options[node.name.to_sym] ||= /.+?/
140
+ elsif node.cat?
141
+ alter_regex_for_custom_routes(node)
142
+ end
143
+ end
113
144
 
114
- path_params = ast.find_all(&:symbol?).map(&:to_sym)
115
-
116
- options = add_wildcard_options(options, formatted, ast)
145
+ options = wildcard_options.merge!(options)
117
146
 
118
- options = normalize_options!(options, path_params, modyoule)
147
+ options = normalize_options!(options, path_params, scope_params[:module])
119
148
 
120
149
  split_options = constraints(options, path_params)
121
150
 
122
- constraints = scope_constraints.merge Hash[split_options[:constraints] || []]
151
+ constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
123
152
 
124
153
  if options_constraints.is_a?(Hash)
125
154
  @defaults = Hash[options_constraints.find_all { |key, default|
126
155
  URL_OPTIONS.include?(key) && (String === default || Integer === default)
127
156
  }].merge @defaults
128
- @blocks = blocks
157
+ @blocks = scope_params[:blocks]
129
158
  constraints.merge! options_constraints
130
159
  else
131
160
  @blocks = blocks(options_constraints)
@@ -148,25 +177,20 @@ module ActionDispatch
148
177
  end
149
178
 
150
179
  def make_route(name, precedence)
151
- route = Journey::Route.new(name,
152
- application,
153
- path,
154
- conditions,
155
- required_defaults,
156
- defaults,
157
- request_method,
158
- precedence,
159
- @internal)
160
-
161
- route
180
+ Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
181
+ required_defaults: required_defaults, defaults: defaults,
182
+ request_method_match: request_method, precedence: precedence,
183
+ scope_options: scope_options, internal: @internal)
162
184
  end
163
185
 
164
186
  def application
165
187
  app(@blocks)
166
188
  end
167
189
 
190
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
191
+
168
192
  def path
169
- build_path @ast, requirements, @anchor
193
+ Journey::Path::Pattern.new(@ast, requirements, JOINED_SEPARATORS, @anchor)
170
194
  end
171
195
 
172
196
  def conditions
@@ -187,16 +211,10 @@ module ActionDispatch
187
211
  end
188
212
  private :request_method
189
213
 
190
- JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
191
-
192
- def build_path(ast, requirements, anchor)
193
- pattern = Journey::Path::Pattern.new(ast, requirements, JOINED_SEPARATORS, anchor)
194
-
214
+ private
195
215
  # Find all the symbol nodes that are adjacent to literal nodes and alter
196
216
  # the regexp so that Journey will partition them into custom routes.
197
- ast.find_all { |node|
198
- next unless node.cat?
199
-
217
+ def alter_regex_for_custom_routes(node)
200
218
  if node.left.literal? && node.right.symbol?
201
219
  symbol = node.right
202
220
  elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
@@ -205,30 +223,15 @@ module ActionDispatch
205
223
  symbol = node.left
206
224
  elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
207
225
  symbol = node.left
208
- else
209
- next
210
226
  end
211
227
 
212
228
  if symbol
213
229
  symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
214
230
  end
215
- }
216
-
217
- pattern
218
- end
219
- private :build_path
231
+ end
220
232
 
221
- private
222
- def add_wildcard_options(options, formatted, path_ast)
223
- # Add a constraint for wildcard route to make it non-greedy and match the
224
- # optional format part of the route by default.
225
- if formatted != false
226
- path_ast.grep(Journey::Nodes::Star).each_with_object({}) { |node, hash|
227
- hash[node.name.to_sym] ||= /.+?/
228
- }.merge options
229
- else
230
- options
231
- end
233
+ def intern(object)
234
+ object.is_a?(String) ? -object : object
232
235
  end
233
236
 
234
237
  def normalize_options!(options, path_params, modyoule)
@@ -279,7 +282,7 @@ module ActionDispatch
279
282
 
280
283
  def verify_regexp_requirements(requirements)
281
284
  requirements.each do |requirement|
282
- if requirement.source =~ ANCHOR_CHARACTERS_REGEX
285
+ if ANCHOR_CHARACTERS_REGEX.match?(requirement.source)
283
286
  raise ArgumentError, "Regexp anchor characters are not allowed in routing requirements: #{requirement.inspect}"
284
287
  end
285
288
 
@@ -308,8 +311,8 @@ module ActionDispatch
308
311
  def check_controller_and_action(path_params, controller, action)
309
312
  hash = check_part(:controller, controller, path_params, {}) do |part|
310
313
  translate_controller(part) {
311
- message = "'#{part}' is not a supported controller name. This can lead to potential routing problems.".dup
312
- message << " See http://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
314
+ message = +"'#{part}' is not a supported controller name. This can lead to potential routing problems."
315
+ message << " See https://guides.rubyonrails.org/routing.html#specifying-a-controller-to-use"
313
316
 
314
317
  raise ArgumentError, message
315
318
  }
@@ -333,8 +336,8 @@ module ActionDispatch
333
336
  end
334
337
 
335
338
  def split_to(to)
336
- if to =~ /#/
337
- to.split("#")
339
+ if /#/.match?(to)
340
+ to.split("#").map!(&:-@)
338
341
  else
339
342
  []
340
343
  end
@@ -342,10 +345,10 @@ module ActionDispatch
342
345
 
343
346
  def add_controller_module(controller, modyoule)
344
347
  if modyoule && !controller.is_a?(Regexp)
345
- if controller =~ %r{\A/}
346
- controller[1..-1]
348
+ if controller&.start_with?("/")
349
+ -controller[1..-1]
347
350
  else
348
- [modyoule, controller].compact.join("/")
351
+ -[modyoule, controller].compact.join("/")
349
352
  end
350
353
  else
351
354
  controller
@@ -354,7 +357,7 @@ module ActionDispatch
354
357
 
355
358
  def translate_controller(controller)
356
359
  return controller if Regexp === controller
357
- return controller.to_s if controller =~ /\A[a-z_0-9][a-z_0-9\/]*\z/
360
+ return controller.to_s if /\A[a-z_0-9][a-z_0-9\/]*\z/.match?(controller)
358
361
 
359
362
  yield
360
363
  end
@@ -385,12 +388,23 @@ module ActionDispatch
385
388
  end
386
389
  end
387
390
 
388
- # Invokes Journey::Router::Utils.normalize_path and ensure that
389
- # (:locale) becomes (/:locale) instead of /(:locale). Except
390
- # for root cases, where the latter is the correct one.
391
+ # Invokes Journey::Router::Utils.normalize_path, then ensures that
392
+ # /(:locale) becomes (/:locale). Except for root cases, where the
393
+ # former is the correct one.
391
394
  def self.normalize_path(path)
392
395
  path = Journey::Router::Utils.normalize_path(path)
393
- path.gsub!(%r{/(\(+)/?}, '\1/') unless path =~ %r{^/\(+[^)]+\)$}
396
+
397
+ # the path for a root URL at this point can be something like
398
+ # "/(/:locale)(/:platform)/(:browser)", and we would want
399
+ # "/(:locale)(/:platform)(/:browser)"
400
+
401
+ # reverse "/(", "/((" etc to "(/", "((/" etc
402
+ path.gsub!(%r{/(\(+)/?}, '\1/')
403
+ # if a path is all optional segments, change the leading "(/" back to
404
+ # "/(" so it evaluates to "/" when interpreted with no options.
405
+ # Unless, however, at least one secondary segment consists of a static
406
+ # part, ex. "(/:locale)(/pages/:page)"
407
+ path.sub!(%r{^(\(+)/}, '/\1') if %r{^(\(+[^)]+\))(\(+/:[^)]+\))*$}.match?(path)
394
408
  path
395
409
  end
396
410
 
@@ -547,16 +561,16 @@ module ActionDispatch
547
561
  # Constrains parameters with a hash of regular expressions
548
562
  # or an object that responds to <tt>matches?</tt>. In addition, constraints
549
563
  # other than path can also be specified with any object
550
- # that responds to <tt>===</tt> (eg. String, Array, Range, etc.).
564
+ # that responds to <tt>===</tt> (e.g. String, Array, Range, etc.).
551
565
  #
552
566
  # match 'path/:id', constraints: { id: /[A-Z]\d{5}/ }, via: :get
553
567
  #
554
568
  # match 'json_only', constraints: { format: 'json' }, via: :get
555
569
  #
556
- # class Whitelist
570
+ # class PermitList
557
571
  # def matches?(request) request.remote_ip == '1.2.3.4' end
558
572
  # end
559
- # match 'path', to: 'c#a', constraints: Whitelist.new, via: :get
573
+ # match 'path', to: 'c#a', constraints: PermitList.new, via: :get
560
574
  #
561
575
  # See <tt>Scoping#constraints</tt> for more examples with its scope
562
576
  # equivalent.
@@ -611,7 +625,7 @@ module ActionDispatch
611
625
  end
612
626
 
613
627
  raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call)
614
- raise ArgumentError, <<-MSG.strip_heredoc unless path
628
+ raise ArgumentError, <<~MSG unless path
615
629
  Must be called with mount point
616
630
 
617
631
  mount SomeRackApp, at: "some_route"
@@ -644,7 +658,7 @@ module ActionDispatch
644
658
 
645
659
  # Query if the following named route was already defined.
646
660
  def has_named_route?(name)
647
- @set.named_routes.key? name
661
+ @set.named_routes.key?(name)
648
662
  end
649
663
 
650
664
  private
@@ -664,10 +678,11 @@ module ActionDispatch
664
678
  def define_generate_prefix(app, name)
665
679
  _route = @set.named_routes.get name
666
680
  _routes = @set
681
+ _url_helpers = @set.url_helpers
667
682
 
668
683
  script_namer = ->(options) do
669
684
  prefix_options = options.slice(*_route.segment_keys)
670
- prefix_options[:relative_url_root] = "".freeze
685
+ prefix_options[:relative_url_root] = ""
671
686
 
672
687
  if options[:_recall]
673
688
  prefix_options.reverse_merge!(options[:_recall].slice(*_route.segment_keys))
@@ -675,7 +690,7 @@ module ActionDispatch
675
690
 
676
691
  # We must actually delete prefix segment keys to avoid passing them to next url_for.
677
692
  _route.segment_keys.each { |k| options.delete(k) }
678
- _routes.url_helpers.send("#{name}_path", prefix_options)
693
+ _url_helpers.public_send("#{name}_path", prefix_options)
679
694
  end
680
695
 
681
696
  app.routes.define_mounted_helper(name, script_namer)
@@ -735,6 +750,14 @@ module ActionDispatch
735
750
  map_method(:delete, args, &block)
736
751
  end
737
752
 
753
+ # Define a route that only recognizes HTTP OPTIONS.
754
+ # For supported arguments, see match[rdoc-ref:Base#match]
755
+ #
756
+ # options 'carrots', to: 'food#carrots'
757
+ def options(*args, &block)
758
+ map_method(:options, args, &block)
759
+ end
760
+
738
761
  private
739
762
  def map_method(method, args, &block)
740
763
  options = args.extract_options!
@@ -982,7 +1005,7 @@ module ActionDispatch
982
1005
  #
983
1006
  # Requests to routes can be constrained based on specific criteria:
984
1007
  #
985
- # constraints(-> (req) { req.env["HTTP_USER_AGENT"] =~ /iPhone/ }) do
1008
+ # constraints(-> (req) { /iPhone/.match?(req.env["HTTP_USER_AGENT"]) }) do
986
1009
  # resources :iphones
987
1010
  # end
988
1011
  #
@@ -992,7 +1015,7 @@ module ActionDispatch
992
1015
  #
993
1016
  # class Iphone
994
1017
  # def self.matches?(request)
995
- # request.env["HTTP_USER_AGENT"] =~ /iPhone/
1018
+ # /iPhone/.match?(request.env["HTTP_USER_AGENT"])
996
1019
  # end
997
1020
  # end
998
1021
  #
@@ -1137,6 +1160,10 @@ module ActionDispatch
1137
1160
  attr_reader :controller, :path, :param
1138
1161
 
1139
1162
  def initialize(entities, api_only, shallow, options = {})
1163
+ if options[:param].to_s.include?(":")
1164
+ raise ArgumentError, ":param option can't contain colons"
1165
+ end
1166
+
1140
1167
  @name = entities.to_s
1141
1168
  @path = (options[:path] || @name).to_s
1142
1169
  @controller = (options[:controller] || @name).to_s
@@ -1158,10 +1185,16 @@ module ActionDispatch
1158
1185
  end
1159
1186
 
1160
1187
  def actions
1188
+ if @except
1189
+ available_actions - Array(@except).map(&:to_sym)
1190
+ else
1191
+ available_actions
1192
+ end
1193
+ end
1194
+
1195
+ def available_actions
1161
1196
  if @only
1162
1197
  Array(@only).map(&:to_sym)
1163
- elsif @except
1164
- default_actions - Array(@except).map(&:to_sym)
1165
1198
  else
1166
1199
  default_actions
1167
1200
  end
@@ -1388,6 +1421,8 @@ module ActionDispatch
1388
1421
  # as a comment on a blog post like <tt>/posts/a-long-permalink/comments/1234</tt>
1389
1422
  # to be shortened to just <tt>/comments/1234</tt>.
1390
1423
  #
1424
+ # Set <tt>shallow: false</tt> on a child resource to ignore a parent's shallow parameter.
1425
+ #
1391
1426
  # [:shallow_path]
1392
1427
  # Prefixes nested shallow routes with the specified path.
1393
1428
  #
@@ -1430,6 +1465,9 @@ module ActionDispatch
1430
1465
  # Allows you to specify the default value for optional +format+
1431
1466
  # segment or disable it by supplying +false+.
1432
1467
  #
1468
+ # [:param]
1469
+ # Allows you to override the default param name of +:id+ in the URL.
1470
+ #
1433
1471
  # === Examples
1434
1472
  #
1435
1473
  # # routes call <tt>Admin::PostsController</tt>
@@ -1570,6 +1608,22 @@ module ActionDispatch
1570
1608
  !parent_resource.singleton? && @scope[:shallow]
1571
1609
  end
1572
1610
 
1611
+ def draw(name)
1612
+ path = @draw_paths.find do |_path|
1613
+ File.exist? "#{_path}/#{name}.rb"
1614
+ end
1615
+
1616
+ unless path
1617
+ msg = "Your router tried to #draw the external file #{name}.rb,\n" \
1618
+ "but the file was not found in:\n\n"
1619
+ msg += @draw_paths.map { |_path| " * #{_path}" }.join("\n")
1620
+ raise ArgumentError, msg
1621
+ end
1622
+
1623
+ route_path = "#{path}/#{name}.rb"
1624
+ instance_eval(File.read(route_path), route_path.to_s)
1625
+ end
1626
+
1573
1627
  # Matches a URL pattern to one or more routes.
1574
1628
  # For more information, see match[rdoc-ref:Base#match].
1575
1629
  #
@@ -1587,7 +1641,7 @@ module ActionDispatch
1587
1641
  when Symbol
1588
1642
  options[:action] = to
1589
1643
  when String
1590
- if to =~ /#/
1644
+ if /#/.match?(to)
1591
1645
  options[:to] = to
1592
1646
  else
1593
1647
  options[:controller] = to
@@ -1644,26 +1698,26 @@ module ActionDispatch
1644
1698
  end
1645
1699
 
1646
1700
  private
1647
-
1648
1701
  def parent_resource
1649
1702
  @scope[:scope_level_resource]
1650
1703
  end
1651
1704
 
1652
1705
  def apply_common_behavior_for(method, resources, options, &block)
1653
1706
  if resources.length > 1
1654
- resources.each { |r| send(method, r, options, &block) }
1707
+ resources.each { |r| public_send(method, r, options, &block) }
1655
1708
  return true
1656
1709
  end
1657
1710
 
1658
- if options.delete(:shallow)
1711
+ if options[:shallow]
1712
+ options.delete(:shallow)
1659
1713
  shallow do
1660
- send(method, resources.pop, options, &block)
1714
+ public_send(method, resources.pop, options, &block)
1661
1715
  end
1662
1716
  return true
1663
1717
  end
1664
1718
 
1665
1719
  if resource_scope?
1666
- nested { send(method, resources.pop, options, &block) }
1720
+ nested { public_send(method, resources.pop, options, &block) }
1667
1721
  return true
1668
1722
  end
1669
1723
 
@@ -1674,7 +1728,7 @@ module ActionDispatch
1674
1728
  scope_options = options.slice!(*RESOURCE_OPTIONS)
1675
1729
  unless scope_options.empty?
1676
1730
  scope(scope_options) do
1677
- send(method, resources.pop, options, &block)
1731
+ public_send(method, resources.pop, options, &block)
1678
1732
  end
1679
1733
  return true
1680
1734
  end
@@ -1804,7 +1858,7 @@ module ActionDispatch
1804
1858
  # and return nil in case it isn't. Otherwise, we pass the invalid name
1805
1859
  # forward so the underlying router engine treats it and raises an exception.
1806
1860
  if as.nil?
1807
- candidate unless candidate !~ /\A[_a-z]/i || has_named_route?(candidate)
1861
+ candidate unless !candidate.match?(/\A[_a-z]/i) || has_named_route?(candidate)
1808
1862
  else
1809
1863
  candidate
1810
1864
  end
@@ -1858,7 +1912,7 @@ module ActionDispatch
1858
1912
  options_constraints = options.delete(:constraints) || {}
1859
1913
 
1860
1914
  path_types = paths.group_by(&:class)
1861
- path_types.fetch(String, []).each do |_path|
1915
+ (path_types[String] || []).each do |_path|
1862
1916
  route_options = options.dup
1863
1917
  if _path && option_path
1864
1918
  raise ArgumentError, "Ambiguous route definition. Both :path and the route path were specified as strings."
@@ -1867,7 +1921,7 @@ module ActionDispatch
1867
1921
  decomposed_match(_path, controller, route_options, _path, to, via, formatted, anchor, options_constraints)
1868
1922
  end
1869
1923
 
1870
- path_types.fetch(Symbol, []).each do |action|
1924
+ (path_types[Symbol] || []).each do |action|
1871
1925
  route_options = options.dup
1872
1926
  decomposed_match(action, controller, route_options, option_path, to, via, formatted, anchor, options_constraints)
1873
1927
  end
@@ -1880,14 +1934,14 @@ module ActionDispatch
1880
1934
 
1881
1935
  path_without_format = path.sub(/\(\.:format\)$/, "")
1882
1936
  if using_match_shorthand?(path_without_format)
1883
- path_without_format.gsub(%r{^/}, "").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1937
+ path_without_format.delete_prefix("/").sub(%r{/([^/]*)$}, '#\1').tr("-", "_")
1884
1938
  else
1885
1939
  nil
1886
1940
  end
1887
1941
  end
1888
1942
 
1889
1943
  def using_match_shorthand?(path)
1890
- path =~ %r{^/?[-\w]+/[-\w/]+$}
1944
+ %r{^/?[-\w]+/[-\w/]+$}.match?(path)
1891
1945
  end
1892
1946
 
1893
1947
  def decomposed_match(path, controller, options, _path, to, via, formatted, anchor, options_constraints)
@@ -1913,7 +1967,7 @@ module ActionDispatch
1913
1967
 
1914
1968
  default_action = options.delete(:action) || @scope[:action]
1915
1969
 
1916
- if action =~ /^[\w\-\/]+$/
1970
+ if /^[\w\-\/]+$/.match?(action)
1917
1971
  default_action ||= action.tr("-", "_") unless action.include?("/")
1918
1972
  else
1919
1973
  action = nil
@@ -1925,7 +1979,7 @@ module ActionDispatch
1925
1979
  name_for_action(options.delete(:as), action)
1926
1980
  end
1927
1981
 
1928
- path = Mapping.normalize_path URI.parser.escape(path), formatted
1982
+ path = Mapping.normalize_path URI::DEFAULT_PARSER.escape(path), formatted
1929
1983
  ast = Journey::Parser.parse path
1930
1984
 
1931
1985
  mapping = Mapping.build(@scope, @set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@@ -1933,9 +1987,7 @@ module ActionDispatch
1933
1987
  end
1934
1988
 
1935
1989
  def match_root_route(options)
1936
- name = has_named_route?(name_for_action(:root, nil)) ? nil : :root
1937
- args = ["/", { as: name, via: :get }.merge!(options)]
1938
-
1990
+ args = ["/", { as: :root, via: :get }.merge(options)]
1939
1991
  match(*args)
1940
1992
  end
1941
1993
  end
@@ -2051,7 +2103,7 @@ module ActionDispatch
2051
2103
  # of routing helpers, e.g:
2052
2104
  #
2053
2105
  # direct :homepage do
2054
- # "http://www.rubyonrails.org"
2106
+ # "https://rubyonrails.org"
2055
2107
  # end
2056
2108
  #
2057
2109
  # direct :commentable do |model|
@@ -2250,6 +2302,7 @@ module ActionDispatch
2250
2302
 
2251
2303
  def initialize(set) #:nodoc:
2252
2304
  @set = set
2305
+ @draw_paths = set.draw_paths
2253
2306
  @scope = Scope.new(path_names: @set.resources_path_names)
2254
2307
  @concerns = {}
2255
2308
  end