actionpack 6.1.3.2 → 7.0.0.alpha2

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 (111) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +103 -387
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -3
  5. data/lib/abstract_controller/asset_paths.rb +1 -1
  6. data/lib/abstract_controller/base.rb +7 -21
  7. data/lib/abstract_controller/caching/fragments.rb +2 -2
  8. data/lib/abstract_controller/caching.rb +1 -1
  9. data/lib/abstract_controller/callbacks.rb +9 -8
  10. data/lib/abstract_controller/collector.rb +4 -2
  11. data/lib/abstract_controller/error.rb +1 -1
  12. data/lib/abstract_controller/helpers.rb +3 -2
  13. data/lib/abstract_controller/logger.rb +1 -1
  14. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  15. data/lib/abstract_controller/translation.rb +0 -2
  16. data/lib/abstract_controller/url_for.rb +4 -6
  17. data/lib/action_controller/api.rb +1 -1
  18. data/lib/action_controller/log_subscriber.rb +3 -1
  19. data/lib/action_controller/metal/conditional_get.rb +38 -1
  20. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  21. data/lib/action_controller/metal/cookies.rb +1 -1
  22. data/lib/action_controller/metal/data_streaming.rb +5 -13
  23. data/lib/action_controller/metal/etag_with_template_digest.rb +1 -1
  24. data/lib/action_controller/metal/exceptions.rb +19 -30
  25. data/lib/action_controller/metal/flash.rb +6 -2
  26. data/lib/action_controller/metal/http_authentication.rb +15 -15
  27. data/lib/action_controller/metal/instrumentation.rb +55 -52
  28. data/lib/action_controller/metal/live.rb +52 -3
  29. data/lib/action_controller/metal/mime_responds.rb +3 -3
  30. data/lib/action_controller/metal/params_wrapper.rb +10 -9
  31. data/lib/action_controller/metal/permissions_policy.rb +1 -1
  32. data/lib/action_controller/metal/query_tags.rb +16 -0
  33. data/lib/action_controller/metal/redirecting.rb +50 -16
  34. data/lib/action_controller/metal/rendering.rb +7 -7
  35. data/lib/action_controller/metal/request_forgery_protection.rb +64 -20
  36. data/lib/action_controller/metal/rescue.rb +1 -1
  37. data/lib/action_controller/metal/streaming.rb +1 -3
  38. data/lib/action_controller/metal/strong_parameters.rb +24 -28
  39. data/lib/action_controller/metal/testing.rb +0 -2
  40. data/lib/action_controller/metal.rb +7 -10
  41. data/lib/action_controller/railtie.rb +42 -5
  42. data/lib/action_controller/test_case.rb +9 -2
  43. data/lib/action_controller.rb +2 -5
  44. data/lib/action_dispatch/http/cache.rb +18 -12
  45. data/lib/action_dispatch/http/content_security_policy.rb +39 -35
  46. data/lib/action_dispatch/http/filter_parameters.rb +5 -0
  47. data/lib/action_dispatch/http/mime_negotiation.rb +13 -3
  48. data/lib/action_dispatch/http/mime_type.rb +9 -11
  49. data/lib/action_dispatch/http/parameters.rb +4 -4
  50. data/lib/action_dispatch/http/permissions_policy.rb +1 -1
  51. data/lib/action_dispatch/http/request.rb +10 -19
  52. data/lib/action_dispatch/http/response.rb +3 -3
  53. data/lib/action_dispatch/http/url.rb +9 -10
  54. data/lib/action_dispatch/journey/formatter.rb +2 -2
  55. data/lib/action_dispatch/journey/gtg/builder.rb +11 -12
  56. data/lib/action_dispatch/journey/gtg/simulator.rb +10 -4
  57. data/lib/action_dispatch/journey/gtg/transition_table.rb +77 -21
  58. data/lib/action_dispatch/journey/nodes/node.rb +70 -5
  59. data/lib/action_dispatch/journey/path/pattern.rb +22 -13
  60. data/lib/action_dispatch/journey/route.rb +5 -12
  61. data/lib/action_dispatch/journey/router/utils.rb +2 -2
  62. data/lib/action_dispatch/journey/router.rb +1 -1
  63. data/lib/action_dispatch/journey/routes.rb +3 -3
  64. data/lib/action_dispatch/journey/visitors.rb +1 -1
  65. data/lib/action_dispatch/journey/visualizer/fsm.js +49 -24
  66. data/lib/action_dispatch/journey/visualizer/index.html.erb +1 -1
  67. data/lib/action_dispatch/middleware/actionable_exceptions.rb +0 -1
  68. data/lib/action_dispatch/middleware/cookies.rb +7 -3
  69. data/lib/action_dispatch/middleware/debug_exceptions.rb +6 -4
  70. data/lib/action_dispatch/middleware/debug_locks.rb +3 -3
  71. data/lib/action_dispatch/middleware/exception_wrapper.rb +4 -0
  72. data/lib/action_dispatch/middleware/flash.rb +9 -11
  73. data/lib/action_dispatch/middleware/host_authorization.rb +9 -17
  74. data/lib/action_dispatch/middleware/remote_ip.rb +16 -4
  75. data/lib/action_dispatch/middleware/session/abstract_store.rb +1 -1
  76. data/lib/action_dispatch/middleware/show_exceptions.rb +7 -9
  77. data/lib/action_dispatch/middleware/stack.rb +27 -9
  78. data/lib/action_dispatch/middleware/static.rb +2 -5
  79. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +1 -1
  80. data/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb +4 -11
  81. data/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb +2 -2
  82. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb +3 -3
  83. data/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb +1 -1
  84. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +4 -4
  85. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +3 -3
  86. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +1 -0
  87. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +28 -18
  88. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +3 -3
  89. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +3 -3
  90. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  91. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +3 -3
  92. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +3 -3
  93. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +5 -14
  94. data/lib/action_dispatch/railtie.rb +8 -2
  95. data/lib/action_dispatch/request/session.rb +43 -13
  96. data/lib/action_dispatch/routing/mapper.rb +44 -72
  97. data/lib/action_dispatch/routing/redirection.rb +0 -2
  98. data/lib/action_dispatch/routing/route_set.rb +9 -6
  99. data/lib/action_dispatch/routing/routes_proxy.rb +1 -1
  100. data/lib/action_dispatch/routing/url_for.rb +1 -2
  101. data/lib/action_dispatch/routing.rb +2 -2
  102. data/lib/action_dispatch/system_test_case.rb +5 -5
  103. data/lib/action_dispatch/system_testing/driver.rb +24 -4
  104. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +10 -6
  105. data/lib/action_dispatch/testing/assertions.rb +2 -5
  106. data/lib/action_dispatch/testing/integration.rb +6 -8
  107. data/lib/action_dispatch/testing/test_process.rb +12 -9
  108. data/lib/action_dispatch.rb +1 -1
  109. data/lib/action_pack/gem_version.rb +4 -4
  110. data/lib/action_pack.rb +1 -1
  111. metadata +21 -20
@@ -1,8 +1,8 @@
1
- <header>
1
+ <header role="banner">
2
2
  <h1>No template for interactive request</h1>
3
3
  </header>
4
4
 
5
- <div id="container">
5
+ <main id="container">
6
6
  <h2><%= h @exception.message %></h2>
7
7
 
8
8
  <p class="summary">
@@ -16,4 +16,4 @@
16
16
  since we expect an HTML template
17
17
  to be rendered for such requests. If that's the case, carry on.
18
18
  </p>
19
- </div>
19
+ </main>
@@ -1,11 +1,11 @@
1
- <header>
1
+ <header role="banner">
2
2
  <h1>Template is missing</h1>
3
3
  </header>
4
4
 
5
- <div id="container">
5
+ <main role="main" id="container">
6
6
  <h2><%= h @exception.message %></h2>
7
7
 
8
8
  <%= render "rescues/source", source_extracts: @source_extracts, show_source_idx: @show_source_idx %>
9
9
  <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
10
10
  <%= render template: "rescues/_request_and_response" %>
11
- </div>
11
+ </main>
@@ -1,7 +1,7 @@
1
- <header>
1
+ <header role="banner">
2
2
  <h1>Routing Error</h1>
3
3
  </header>
4
- <div id="container">
4
+ <main role="main" id="container">
5
5
  <h2><%= h @exception.message %></h2>
6
6
  <% unless @exception.failures.empty? %>
7
7
  <p>
@@ -29,4 +29,4 @@
29
29
  <% end %>
30
30
 
31
31
  <%= render template: "rescues/_request_and_response" %>
32
- </div>
32
+ </main>
@@ -1,11 +1,11 @@
1
- <header>
1
+ <header role="banner">
2
2
  <h1>
3
3
  <%= @exception.cause.class.to_s %> in
4
4
  <%= @request.parameters["controller"].camelize if @request.parameters["controller"] %>#<%= @request.parameters["action"] %>
5
5
  </h1>
6
6
  </header>
7
7
 
8
- <div id="container">
8
+ <main role="main" id="container">
9
9
  <p>
10
10
  Showing <i><%= @exception.file_name %></i> where line <b>#<%= @exception.line_number %></b> raised:
11
11
  </p>
@@ -17,4 +17,4 @@
17
17
 
18
18
  <%= render "rescues/trace", traces: @traces, trace_to_show: @trace_to_show %>
19
19
  <%= render template: "rescues/_request_and_response" %>
20
- </div>
20
+ </main>
@@ -1,6 +1,6 @@
1
- <header>
1
+ <header role="banner">
2
2
  <h1>Unknown action</h1>
3
3
  </header>
4
- <div id="container">
4
+ <main role="main" id="container">
5
5
  <%= render "rescues/message_and_suggestions", exception: @exception %>
6
- </div>
6
+ </main>
@@ -51,22 +51,13 @@
51
51
  }
52
52
 
53
53
  @media (prefers-color-scheme: dark) {
54
- body {
55
- background-color: #222;
56
- color: #ECECEC;
57
- }
58
-
59
54
  #route_table tbody tr:nth-child(odd) {
60
- background: #333;
61
- }
62
-
63
- #route_table tbody tr:nth-child(even) {
64
- background: #444;
55
+ background: #282828;
65
56
  }
66
57
 
67
- #route_table tbody.exact_matches,
68
- #route_table tbody.fuzzy_matches {
69
- color: #333;
58
+ #route_table tbody.exact_matches tr,
59
+ #route_table tbody.fuzzy_matches tr {
60
+ background: DarkSlateGrey;
70
61
  }
71
62
  }
72
63
  <% end %>
@@ -104,7 +95,7 @@
104
95
  </tbody>
105
96
  </table>
106
97
 
107
- <script type='text/javascript'>
98
+ <script>
108
99
  // support forEach iterator on NodeList
109
100
  NodeList.prototype.forEach = Array.prototype.forEach;
110
101
 
@@ -24,6 +24,8 @@ module ActionDispatch
24
24
  config.action_dispatch.use_cookies_with_metadata = false
25
25
  config.action_dispatch.perform_deep_munge = true
26
26
  config.action_dispatch.request_id_header = "X-Request-Id"
27
+ config.action_dispatch.return_only_request_media_type_on_content_type = true
28
+ config.action_dispatch.log_rescued_responses = true
27
29
 
28
30
  config.action_dispatch.default_headers = {
29
31
  "X-Frame-Options" => "SAMEORIGIN",
@@ -41,8 +43,12 @@ module ActionDispatch
41
43
  initializer "action_dispatch.configure" do |app|
42
44
  ActionDispatch::Http::URL.secure_protocol = app.config.force_ssl
43
45
  ActionDispatch::Http::URL.tld_length = app.config.action_dispatch.tld_length
44
- ActionDispatch::Request.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
45
- ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
46
+
47
+ ActiveSupport.on_load(:action_dispatch_request) do
48
+ self.ignore_accept_header = app.config.action_dispatch.ignore_accept_header
49
+ self.return_only_media_type_on_content_type = app.config.action_dispatch.return_only_request_media_type_on_content_type
50
+ ActionDispatch::Request::Utils.perform_deep_munge = app.config.action_dispatch.perform_deep_munge
51
+ end
46
52
 
47
53
  ActiveSupport.on_load(:action_dispatch_response) do
48
54
  self.default_charset = app.config.action_dispatch.default_charset || app.config.encoding
@@ -6,6 +6,7 @@ module ActionDispatch
6
6
  class Request
7
7
  # Session is responsible for lazily loading the session from store.
8
8
  class Session # :nodoc:
9
+ DisabledSessionError = Class.new(StandardError)
9
10
  ENV_SESSION_KEY = Rack::RACK_SESSION # :nodoc:
10
11
  ENV_SESSION_OPTIONS_KEY = Rack::RACK_SESSION_OPTIONS # :nodoc:
11
12
 
@@ -23,6 +24,12 @@ module ActionDispatch
23
24
  session
24
25
  end
25
26
 
27
+ def self.disabled(req)
28
+ new(nil, req, enabled: false).tap do
29
+ Session::Options.set(req, Session::Options.new(nil, { id: nil }))
30
+ end
31
+ end
32
+
26
33
  def self.find(req)
27
34
  req.get_header ENV_SESSION_KEY
28
35
  end
@@ -31,7 +38,11 @@ module ActionDispatch
31
38
  req.set_header ENV_SESSION_KEY, session
32
39
  end
33
40
 
34
- class Options #:nodoc:
41
+ def self.delete(req)
42
+ req.delete_header ENV_SESSION_KEY
43
+ end
44
+
45
+ class Options # :nodoc:
35
46
  def self.set(req, options)
36
47
  req.set_header ENV_SESSION_OPTIONS_KEY, options
37
48
  end
@@ -60,30 +71,38 @@ module ActionDispatch
60
71
  def values_at(*args); @delegate.values_at(*args); end
61
72
  end
62
73
 
63
- def initialize(by, req)
74
+ def initialize(by, req, enabled: true)
64
75
  @by = by
65
76
  @req = req
66
77
  @delegate = {}
67
78
  @loaded = false
68
79
  @exists = nil # We haven't checked yet.
80
+ @enabled = enabled
69
81
  end
70
82
 
71
83
  def id
72
84
  options.id(@req)
73
85
  end
74
86
 
87
+ def enabled?
88
+ @enabled
89
+ end
90
+
75
91
  def options
76
92
  Options.find @req
77
93
  end
78
94
 
79
95
  def destroy
80
96
  clear
81
- options = self.options || {}
82
- @by.send(:delete_session, @req, options.id(@req), options)
83
97
 
84
- # Load the new sid to be written with the response.
85
- @loaded = false
86
- load_for_write!
98
+ if enabled?
99
+ options = self.options || {}
100
+ @by.send(:delete_session, @req, options.id(@req), options)
101
+
102
+ # Load the new sid to be written with the response.
103
+ @loaded = false
104
+ load_for_write!
105
+ end
87
106
  end
88
107
 
89
108
  # Returns value of the key stored in the session or
@@ -135,7 +154,7 @@ module ActionDispatch
135
154
 
136
155
  # Clears the session.
137
156
  def clear
138
- load_for_write!
157
+ load_for_delete!
139
158
  @delegate.clear
140
159
  end
141
160
 
@@ -163,7 +182,7 @@ module ActionDispatch
163
182
 
164
183
  # Deletes given key from the session.
165
184
  def delete(key)
166
- load_for_write!
185
+ load_for_delete!
167
186
  @delegate.delete key.to_s
168
187
  end
169
188
 
@@ -199,6 +218,7 @@ module ActionDispatch
199
218
  end
200
219
 
201
220
  def exists?
221
+ return false unless enabled?
202
222
  return @exists unless @exists.nil?
203
223
  @exists = @by.send(:session_exists?, @req)
204
224
  end
@@ -227,13 +247,23 @@ module ActionDispatch
227
247
  end
228
248
 
229
249
  def load_for_write!
230
- load! unless loaded?
250
+ if enabled?
251
+ load! unless loaded?
252
+ else
253
+ raise DisabledSessionError, "Your application has sessions disabled. To write to the session you must first configure a session store"
254
+ end
255
+ end
256
+
257
+ def load_for_delete!
258
+ load! if enabled? && !loaded?
231
259
  end
232
260
 
233
261
  def load!
234
- id, session = @by.load_session @req
235
- options[:id] = id
236
- @delegate.replace(session.stringify_keys)
262
+ if enabled?
263
+ id, session = @by.load_session @req
264
+ options[:id] = id
265
+ @delegate.replace(session.stringify_keys)
266
+ end
237
267
  @loaded = true
238
268
  end
239
269
  end
@@ -4,7 +4,6 @@ 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"
8
7
  require "action_dispatch/routing/redirection"
9
8
  require "action_dispatch/routing/endpoint"
10
9
 
@@ -13,7 +12,7 @@ module ActionDispatch
13
12
  class Mapper
14
13
  URL_OPTIONS = [:protocol, :subdomain, :domain, :host, :port]
15
14
 
16
- class Constraints < Routing::Endpoint #:nodoc:
15
+ class Constraints < Routing::Endpoint # :nodoc:
17
16
  attr_reader :app, :constraints
18
17
 
19
18
  SERVE = ->(app, req) { app.serve req }
@@ -67,11 +66,11 @@ module ActionDispatch
67
66
  end
68
67
  end
69
68
 
70
- class Mapping #:nodoc:
69
+ class Mapping # :nodoc:
71
70
  ANCHOR_CHARACTERS_REGEX = %r{\A(\\A|\^)|(\\Z|\\z|\$)\Z}
72
71
  OPTIONAL_FORMAT_REGEX = %r{(?:\(\.:format\)+|\.:format|/)\Z}
73
72
 
74
- attr_reader :requirements, :defaults, :to, :default_controller,
73
+ attr_reader :path, :requirements, :defaults, :to, :default_controller,
75
74
  :default_action, :required_defaults, :ast, :scope_options
76
75
 
77
76
  def self.build(scope, set, ast, controller, default_action, to, via, formatted, options_constraints, anchor, options)
@@ -122,31 +121,17 @@ module ActionDispatch
122
121
  @to = intern(to)
123
122
  @default_controller = intern(controller)
124
123
  @default_action = intern(default_action)
125
- @ast = ast
126
124
  @anchor = anchor
127
125
  @via = via
128
126
  @internal = options.delete(:internal)
129
127
  @scope_options = scope_params[:options]
128
+ ast = Journey::Ast.new(ast, formatted)
130
129
 
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
144
-
145
- options = wildcard_options.merge!(options)
130
+ options = ast.wildcard_options.merge!(options)
146
131
 
147
- options = normalize_options!(options, path_params, scope_params[:module])
132
+ options = normalize_options!(options, ast.path_params, scope_params[:module])
148
133
 
149
- split_options = constraints(options, path_params)
134
+ split_options = constraints(options, ast.path_params)
150
135
 
151
136
  constraints = scope_params[:constraints].merge Hash[split_options[:constraints] || []]
152
137
 
@@ -160,7 +145,7 @@ module ActionDispatch
160
145
  @blocks = blocks(options_constraints)
161
146
  end
162
147
 
163
- requirements, conditions = split_constraints path_params, constraints
148
+ requirements, conditions = split_constraints ast.path_params, constraints
164
149
  verify_regexp_requirements requirements.map(&:last).grep(Regexp)
165
150
 
166
151
  formats = normalize_format(formatted)
@@ -169,13 +154,18 @@ module ActionDispatch
169
154
  @conditions = Hash[conditions]
170
155
  @defaults = formats[:defaults].merge(@defaults).merge(normalize_defaults(options))
171
156
 
172
- if path_params.include?(:action) && !@requirements.key?(:action)
157
+ if ast.path_params.include?(:action) && !@requirements.key?(:action)
173
158
  @defaults[:action] ||= "index"
174
159
  end
175
160
 
176
161
  @required_defaults = (split_options[:required_defaults] || []).map(&:first)
162
+
163
+ ast.requirements = @requirements
164
+ @path = Journey::Path::Pattern.new(ast, @requirements, JOINED_SEPARATORS, @anchor)
177
165
  end
178
166
 
167
+ JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
168
+
179
169
  def make_route(name, precedence)
180
170
  Journey::Route.new(name: name, app: application, path: path, constraints: conditions,
181
171
  required_defaults: required_defaults, defaults: defaults,
@@ -187,12 +177,6 @@ module ActionDispatch
187
177
  app(@blocks)
188
178
  end
189
179
 
190
- JOINED_SEPARATORS = SEPARATORS.join # :nodoc:
191
-
192
- def path
193
- Journey::Path::Pattern.new(@ast, requirements, JOINED_SEPARATORS, @anchor)
194
- end
195
-
196
180
  def conditions
197
181
  build_conditions @conditions, @set.request_class
198
182
  end
@@ -212,24 +196,6 @@ module ActionDispatch
212
196
  private :request_method
213
197
 
214
198
  private
215
- # Find all the symbol nodes that are adjacent to literal nodes and alter
216
- # the regexp so that Journey will partition them into custom routes.
217
- def alter_regex_for_custom_routes(node)
218
- if node.left.literal? && node.right.symbol?
219
- symbol = node.right
220
- elsif node.left.literal? && node.right.cat? && node.right.left.symbol?
221
- symbol = node.right.left
222
- elsif node.left.symbol? && node.right.literal?
223
- symbol = node.left
224
- elsif node.left.symbol? && node.right.cat? && node.right.left.literal?
225
- symbol = node.left
226
- end
227
-
228
- if symbol
229
- symbol.regexp = /(?:#{Regexp.union(symbol.regexp, '-')})+/
230
- end
231
- end
232
-
233
199
  def intern(object)
234
200
  object.is_a?(String) ? -object : object
235
201
  end
@@ -956,7 +922,7 @@ module ActionDispatch
956
922
  # namespace :admin, as: "sekret" do
957
923
  # resources :posts
958
924
  # end
959
- def namespace(path, options = {})
925
+ def namespace(path, options = {}, &block)
960
926
  path = path.to_s
961
927
 
962
928
  defaults = {
@@ -967,7 +933,7 @@ module ActionDispatch
967
933
  }
968
934
 
969
935
  path_scope(options.delete(:path) { path }) do
970
- scope(defaults.merge!(options)) { yield }
936
+ scope(defaults.merge!(options), &block)
971
937
  end
972
938
  end
973
939
 
@@ -1026,8 +992,8 @@ module ActionDispatch
1026
992
  # constraints(Iphone) do
1027
993
  # resources :iphones
1028
994
  # end
1029
- def constraints(constraints = {})
1030
- scope(constraints: constraints) { yield }
995
+ def constraints(constraints = {}, &block)
996
+ scope(constraints: constraints, &block)
1031
997
  end
1032
998
 
1033
999
  # Allows you to set default parameters for a route, such as this:
@@ -1156,7 +1122,7 @@ module ActionDispatch
1156
1122
  RESOURCE_OPTIONS = [:as, :controller, :path, :only, :except, :param, :concerns]
1157
1123
  CANONICAL_ACTIONS = %w(index create new show update destroy)
1158
1124
 
1159
- class Resource #:nodoc:
1125
+ class Resource # :nodoc:
1160
1126
  attr_reader :controller, :path, :param
1161
1127
 
1162
1128
  def initialize(entities, api_only, shallow, options = {})
@@ -1251,7 +1217,7 @@ module ActionDispatch
1251
1217
  def singleton?; false; end
1252
1218
  end
1253
1219
 
1254
- class SingletonResource < Resource #:nodoc:
1220
+ class SingletonResource < Resource # :nodoc:
1255
1221
  def initialize(entities, api_only, shallow, options)
1256
1222
  super
1257
1223
  @as = nil
@@ -1307,6 +1273,16 @@ module ActionDispatch
1307
1273
  # DELETE /profile
1308
1274
  # POST /profile
1309
1275
  #
1276
+ # If you want instances of a model to work with this resource via
1277
+ # record identification (e.g. in +form_with+ or +redirect_to+), you
1278
+ # will need to call resolve[rdoc-ref:CustomUrls#resolve]:
1279
+ #
1280
+ # resource :profile
1281
+ # resolve('Profile') { [:profile] }
1282
+ #
1283
+ # # Enables this to work with singular routes:
1284
+ # form_with(model: @profile) {}
1285
+ #
1310
1286
  # === Options
1311
1287
  # Takes same options as resources[rdoc-ref:#resources]
1312
1288
  def resource(*resources, &block)
@@ -1517,15 +1493,13 @@ module ActionDispatch
1517
1493
  # with GET, and route to the search action of +PhotosController+. It will also
1518
1494
  # create the <tt>search_photos_url</tt> and <tt>search_photos_path</tt>
1519
1495
  # route helpers.
1520
- def collection
1496
+ def collection(&block)
1521
1497
  unless resource_scope?
1522
1498
  raise ArgumentError, "can't use collection outside resource(s) scope"
1523
1499
  end
1524
1500
 
1525
1501
  with_scope_level(:collection) do
1526
- path_scope(parent_resource.collection_scope) do
1527
- yield
1528
- end
1502
+ path_scope(parent_resource.collection_scope, &block)
1529
1503
  end
1530
1504
  end
1531
1505
 
@@ -1540,7 +1514,7 @@ module ActionDispatch
1540
1514
  # This will recognize <tt>/photos/1/preview</tt> with GET, and route to the
1541
1515
  # preview action of +PhotosController+. It will also create the
1542
1516
  # <tt>preview_photo_url</tt> and <tt>preview_photo_path</tt> helpers.
1543
- def member
1517
+ def member(&block)
1544
1518
  unless resource_scope?
1545
1519
  raise ArgumentError, "can't use member outside resource(s) scope"
1546
1520
  end
@@ -1548,27 +1522,25 @@ module ActionDispatch
1548
1522
  with_scope_level(:member) do
1549
1523
  if shallow?
1550
1524
  shallow_scope {
1551
- path_scope(parent_resource.member_scope) { yield }
1525
+ path_scope(parent_resource.member_scope, &block)
1552
1526
  }
1553
1527
  else
1554
- path_scope(parent_resource.member_scope) { yield }
1528
+ path_scope(parent_resource.member_scope, &block)
1555
1529
  end
1556
1530
  end
1557
1531
  end
1558
1532
 
1559
- def new
1533
+ def new(&block)
1560
1534
  unless resource_scope?
1561
1535
  raise ArgumentError, "can't use new outside resource(s) scope"
1562
1536
  end
1563
1537
 
1564
1538
  with_scope_level(:new) do
1565
- path_scope(parent_resource.new_scope(action_path(:new))) do
1566
- yield
1567
- end
1539
+ path_scope(parent_resource.new_scope(action_path(:new)), &block)
1568
1540
  end
1569
1541
  end
1570
1542
 
1571
- def nested
1543
+ def nested(&block)
1572
1544
  unless resource_scope?
1573
1545
  raise ArgumentError, "can't use nested outside resource(s) scope"
1574
1546
  end
@@ -1577,12 +1549,12 @@ module ActionDispatch
1577
1549
  if shallow? && shallow_nesting_depth >= 1
1578
1550
  shallow_scope do
1579
1551
  path_scope(parent_resource.nested_scope) do
1580
- scope(nested_options) { yield }
1552
+ scope(nested_options, &block)
1581
1553
  end
1582
1554
  end
1583
1555
  else
1584
1556
  path_scope(parent_resource.nested_scope) do
1585
- scope(nested_options) { yield }
1557
+ scope(nested_options, &block)
1586
1558
  end
1587
1559
  end
1588
1560
  end
@@ -1768,10 +1740,10 @@ module ActionDispatch
1768
1740
  @scope = @scope.parent
1769
1741
  end
1770
1742
 
1771
- def resource_scope(resource)
1743
+ def resource_scope(resource, &block)
1772
1744
  @scope = @scope.new(scope_level_resource: resource)
1773
1745
 
1774
- controller(resource.resource_scope) { yield }
1746
+ controller(resource.resource_scope, &block)
1775
1747
  ensure
1776
1748
  @scope = @scope.parent
1777
1749
  end
@@ -1889,7 +1861,7 @@ module ActionDispatch
1889
1861
  end
1890
1862
 
1891
1863
  def map_match(paths, options)
1892
- if options[:on] && !VALID_ON_OPTIONS.include?(options[:on])
1864
+ if (on = options[:on]) && !VALID_ON_OPTIONS.include?(on)
1893
1865
  raise ArgumentError, "Unknown scope #{on.inspect} given to :on"
1894
1866
  end
1895
1867
 
@@ -2300,7 +2272,7 @@ module ActionDispatch
2300
2272
  NULL = Scope.new(nil, nil)
2301
2273
  end
2302
2274
 
2303
- def initialize(set) #:nodoc:
2275
+ def initialize(set) # :nodoc:
2304
2276
  @set = set
2305
2277
  @draw_paths = set.draw_paths
2306
2278
  @scope = Scope.new(path_names: @set.resources_path_names)