actionpack 6.0.3.1 → 6.1.0.rc2

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 (115) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +260 -219
  3. data/MIT-LICENSE +1 -1
  4. data/lib/abstract_controller.rb +1 -0
  5. data/lib/abstract_controller/base.rb +35 -2
  6. data/lib/abstract_controller/callbacks.rb +2 -2
  7. data/lib/abstract_controller/helpers.rb +105 -90
  8. data/lib/abstract_controller/rendering.rb +9 -9
  9. data/lib/abstract_controller/translation.rb +8 -2
  10. data/lib/action_controller.rb +2 -3
  11. data/lib/action_controller/api.rb +2 -2
  12. data/lib/action_controller/base.rb +4 -2
  13. data/lib/action_controller/caching.rb +0 -1
  14. data/lib/action_controller/log_subscriber.rb +3 -3
  15. data/lib/action_controller/metal.rb +2 -2
  16. data/lib/action_controller/metal/conditional_get.rb +10 -2
  17. data/lib/action_controller/metal/content_security_policy.rb +1 -1
  18. data/lib/action_controller/metal/cookies.rb +3 -1
  19. data/lib/action_controller/metal/data_streaming.rb +1 -1
  20. data/lib/action_controller/metal/etag_with_template_digest.rb +2 -4
  21. data/lib/action_controller/metal/exceptions.rb +33 -0
  22. data/lib/action_controller/metal/head.rb +7 -4
  23. data/lib/action_controller/metal/helpers.rb +11 -1
  24. data/lib/action_controller/metal/http_authentication.rb +4 -2
  25. data/lib/action_controller/metal/implicit_render.rb +1 -1
  26. data/lib/action_controller/metal/instrumentation.rb +11 -9
  27. data/lib/action_controller/metal/live.rb +1 -1
  28. data/lib/action_controller/metal/logging.rb +20 -0
  29. data/lib/action_controller/metal/mime_responds.rb +6 -2
  30. data/lib/action_controller/metal/parameter_encoding.rb +35 -4
  31. data/lib/action_controller/metal/params_wrapper.rb +14 -8
  32. data/lib/action_controller/metal/permissions_policy.rb +46 -0
  33. data/lib/action_controller/metal/redirecting.rb +1 -1
  34. data/lib/action_controller/metal/rendering.rb +6 -0
  35. data/lib/action_controller/metal/request_forgery_protection.rb +48 -24
  36. data/lib/action_controller/metal/rescue.rb +1 -1
  37. data/lib/action_controller/metal/strong_parameters.rb +103 -15
  38. data/lib/action_controller/renderer.rb +24 -13
  39. data/lib/action_controller/test_case.rb +62 -56
  40. data/lib/action_dispatch.rb +3 -2
  41. data/lib/action_dispatch/http/cache.rb +12 -10
  42. data/lib/action_dispatch/http/content_disposition.rb +2 -2
  43. data/lib/action_dispatch/http/content_security_policy.rb +5 -1
  44. data/lib/action_dispatch/http/filter_parameters.rb +1 -1
  45. data/lib/action_dispatch/http/filter_redirect.rb +1 -1
  46. data/lib/action_dispatch/http/headers.rb +3 -2
  47. data/lib/action_dispatch/http/mime_negotiation.rb +20 -8
  48. data/lib/action_dispatch/http/mime_type.rb +28 -15
  49. data/lib/action_dispatch/http/parameters.rb +1 -19
  50. data/lib/action_dispatch/http/permissions_policy.rb +173 -0
  51. data/lib/action_dispatch/http/request.rb +26 -8
  52. data/lib/action_dispatch/http/response.rb +17 -16
  53. data/lib/action_dispatch/http/url.rb +3 -2
  54. data/lib/action_dispatch/journey.rb +0 -2
  55. data/lib/action_dispatch/journey/formatter.rb +53 -28
  56. data/lib/action_dispatch/journey/gtg/builder.rb +22 -36
  57. data/lib/action_dispatch/journey/gtg/simulator.rb +8 -7
  58. data/lib/action_dispatch/journey/gtg/transition_table.rb +6 -4
  59. data/lib/action_dispatch/journey/nfa/dot.rb +0 -11
  60. data/lib/action_dispatch/journey/nodes/node.rb +4 -3
  61. data/lib/action_dispatch/journey/parser.rb +13 -13
  62. data/lib/action_dispatch/journey/parser.y +1 -1
  63. data/lib/action_dispatch/journey/path/pattern.rb +13 -18
  64. data/lib/action_dispatch/journey/route.rb +7 -18
  65. data/lib/action_dispatch/journey/router.rb +26 -30
  66. data/lib/action_dispatch/journey/router/utils.rb +6 -4
  67. data/lib/action_dispatch/middleware/actionable_exceptions.rb +9 -2
  68. data/lib/action_dispatch/middleware/cookies.rb +74 -33
  69. data/lib/action_dispatch/middleware/debug_exceptions.rb +10 -17
  70. data/lib/action_dispatch/middleware/debug_view.rb +1 -1
  71. data/lib/action_dispatch/middleware/exception_wrapper.rb +29 -17
  72. data/lib/action_dispatch/middleware/host_authorization.rb +25 -5
  73. data/lib/action_dispatch/middleware/public_exceptions.rb +1 -1
  74. data/lib/action_dispatch/middleware/remote_ip.rb +5 -4
  75. data/lib/action_dispatch/middleware/request_id.rb +4 -5
  76. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -2
  77. data/lib/action_dispatch/middleware/session/cookie_store.rb +2 -2
  78. data/lib/action_dispatch/middleware/ssl.rb +9 -6
  79. data/lib/action_dispatch/middleware/stack.rb +18 -0
  80. data/lib/action_dispatch/middleware/static.rb +154 -93
  81. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +22 -0
  82. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +2 -5
  83. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.html.erb +2 -2
  84. data/lib/action_dispatch/middleware/templates/rescues/invalid_statement.text.erb +2 -2
  85. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +100 -8
  86. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  87. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +12 -1
  88. data/lib/action_dispatch/railtie.rb +3 -2
  89. data/lib/action_dispatch/request/session.rb +2 -8
  90. data/lib/action_dispatch/request/utils.rb +26 -2
  91. data/lib/action_dispatch/routing/inspector.rb +8 -7
  92. data/lib/action_dispatch/routing/mapper.rb +102 -71
  93. data/lib/action_dispatch/routing/polymorphic_routes.rb +12 -11
  94. data/lib/action_dispatch/routing/redirection.rb +3 -3
  95. data/lib/action_dispatch/routing/route_set.rb +49 -41
  96. data/lib/action_dispatch/routing/url_for.rb +1 -0
  97. data/lib/action_dispatch/system_test_case.rb +29 -24
  98. data/lib/action_dispatch/system_testing/browser.rb +33 -27
  99. data/lib/action_dispatch/system_testing/driver.rb +6 -7
  100. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +47 -6
  101. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +4 -7
  102. data/lib/action_dispatch/testing/assertions.rb +1 -1
  103. data/lib/action_dispatch/testing/assertions/response.rb +2 -4
  104. data/lib/action_dispatch/testing/assertions/routing.rb +5 -5
  105. data/lib/action_dispatch/testing/integration.rb +38 -27
  106. data/lib/action_dispatch/testing/test_process.rb +29 -4
  107. data/lib/action_dispatch/testing/test_request.rb +3 -3
  108. data/lib/action_pack.rb +1 -1
  109. data/lib/action_pack/gem_version.rb +3 -3
  110. metadata +20 -21
  111. data/lib/action_controller/metal/force_ssl.rb +0 -58
  112. data/lib/action_dispatch/http/parameter_filter.rb +0 -12
  113. data/lib/action_dispatch/journey/nfa/builder.rb +0 -78
  114. data/lib/action_dispatch/journey/nfa/simulator.rb +0 -47
  115. data/lib/action_dispatch/journey/nfa/transition_table.rb +0 -119
@@ -93,7 +93,7 @@ module ActionController
93
93
  # the ones passed as arguments:
94
94
  #
95
95
  # class MyAPIBaseController < ActionController::Metal
96
- # ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
96
+ # ActionController::API.without_modules(:UrlFor).each do |left|
97
97
  # include left
98
98
  # end
99
99
  # end
@@ -120,9 +120,9 @@ module ActionController
120
120
  BasicImplicitRender,
121
121
  StrongParameters,
122
122
 
123
- ForceSSL,
124
123
  DataStreaming,
125
124
  DefaultHeaders,
125
+ Logging,
126
126
 
127
127
  # Before callbacks should also be executed as early as possible, so
128
128
  # also include them at the bottom.
@@ -226,13 +226,14 @@ module ActionController
226
226
  FormBuilder,
227
227
  RequestForgeryProtection,
228
228
  ContentSecurityPolicy,
229
- ForceSSL,
229
+ PermissionsPolicy,
230
230
  Streaming,
231
231
  DataStreaming,
232
232
  HttpAuthentication::Basic::ControllerMethods,
233
233
  HttpAuthentication::Digest::ControllerMethods,
234
234
  HttpAuthentication::Token::ControllerMethods,
235
235
  DefaultHeaders,
236
+ Logging,
236
237
 
237
238
  # Before callbacks should also be executed as early as possible, so
238
239
  # also include them at the bottom.
@@ -261,9 +262,10 @@ module ActionController
261
262
  @_view_renderer @_lookup_context @_routes @_view_runtime @_db_runtime @_helper_proxy
262
263
  )
263
264
 
264
- def _protected_ivars # :nodoc:
265
+ def _protected_ivars
265
266
  PROTECTED_IVARS
266
267
  end
268
+ private :_protected_ivars
267
269
 
268
270
  ActiveSupport.run_load_hooks(:action_controller_base, self)
269
271
  ActiveSupport.run_load_hooks(:action_controller, self)
@@ -22,7 +22,6 @@ module ActionController
22
22
  # config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
23
23
  # config.action_controller.cache_store = MyOwnStore.new('parameter')
24
24
  module Caching
25
- extend ActiveSupport::Autoload
26
25
  extend ActiveSupport::Concern
27
26
 
28
27
  included do
@@ -11,6 +11,7 @@ module ActionController
11
11
  params = payload[:params].except(*INTERNAL_PARAMS)
12
12
  format = payload[:format]
13
13
  format = format.to_s.upcase if format.is_a?(Symbol)
14
+ format = "*/*" if format.nil?
14
15
 
15
16
  info "Processing by #{payload[:controller]}##{payload[:action]} as #{format}"
16
17
  info " Parameters: #{params.inspect}" unless params.empty?
@@ -22,15 +23,14 @@ module ActionController
22
23
  additions = ActionController::Base.log_process_action(payload)
23
24
  status = payload[:status]
24
25
 
25
- if status.nil? && payload[:exception].present?
26
- exception_class_name = payload[:exception].first
26
+ if status.nil? && (exception_class_name = payload[:exception].first)
27
27
  status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
28
28
  end
29
29
 
30
30
  additions << "Allocations: #{event.allocations}"
31
31
 
32
32
  message = +"Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
33
- message << " (#{additions.join(" | ")})" unless additions.empty?
33
+ message << " (#{additions.join(" | ")})"
34
34
  message << "\n\n" if defined?(Rails.env) && Rails.env.development?
35
35
 
36
36
  message
@@ -126,7 +126,7 @@ module ActionController
126
126
  # ==== Returns
127
127
  # * <tt>string</tt>
128
128
  def self.controller_name
129
- @controller_name ||= name.demodulize.sub(/Controller$/, "").underscore
129
+ @controller_name ||= (name.demodulize.delete_suffix("Controller").underscore unless anonymous?)
130
130
  end
131
131
 
132
132
  def self.make_response!(request)
@@ -135,7 +135,7 @@ module ActionController
135
135
  end
136
136
  end
137
137
 
138
- def self.binary_params_for?(action) # :nodoc:
138
+ def self.action_encoding_template(action) # :nodoc:
139
139
  false
140
140
  end
141
141
 
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/core_ext/object/try"
4
+ require "active_support/core_ext/integer/time"
5
+
3
6
  module ActionController
4
7
  module ConditionalGet
5
8
  extend ActiveSupport::Concern
@@ -17,12 +20,12 @@ module ActionController
17
20
  # of cached pages.
18
21
  #
19
22
  # class InvoicesController < ApplicationController
20
- # etag { current_user.try :id }
23
+ # etag { current_user&.id }
21
24
  #
22
25
  # def show
23
26
  # # Etag will differ even for the same invoice when it's viewed by a different current_user
24
27
  # @invoice = Invoice.find(params[:id])
25
- # fresh_when(@invoice)
28
+ # fresh_when etag: @invoice
26
29
  # end
27
30
  # end
28
31
  def etag(&etagger)
@@ -234,6 +237,11 @@ module ActionController
234
237
  # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds
235
238
  # expires_in 3.hours, public: true, stale_while_revalidate: 60.seconds, stale_if_error: 5.minutes
236
239
  #
240
+ # HTTP Cache-Control Extensions other values: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
241
+ # Any additional key-value pairs are concatenated onto the `Cache-Control` header in the response:
242
+ #
243
+ # expires_in 3.hours, public: true, "s-maxage": 3.hours, "no-transform": true
244
+ #
237
245
  # The method will also ensure an HTTP Date header for client compatibility.
238
246
  def expires_in(seconds, options = {})
239
247
  response.cache_control.merge!(
@@ -45,7 +45,7 @@ module ActionController #:nodoc:
45
45
  end
46
46
 
47
47
  def current_content_security_policy
48
- request.content_security_policy.try(:clone) || ActionDispatch::ContentSecurityPolicy.new
48
+ request.content_security_policy&.clone || ActionDispatch::ContentSecurityPolicy.new
49
49
  end
50
50
  end
51
51
  end
@@ -9,7 +9,9 @@ module ActionController #:nodoc:
9
9
  end
10
10
 
11
11
  private
12
- def cookies
12
+ # The cookies for the current request. See ActionDispatch::Cookies for
13
+ # more information.
14
+ def cookies # :doc:
13
15
  request.cookie_jar
14
16
  end
15
17
  end
@@ -53,7 +53,7 @@ module ActionController #:nodoc:
53
53
  #
54
54
  # Show a 404 page in the browser:
55
55
  #
56
- # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
56
+ # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', disposition: 'inline', status: 404
57
57
  #
58
58
  # Read about the other Content-* HTTP headers if you'd like to
59
59
  # provide the user with more information (such as Content-Description) in
@@ -26,10 +26,8 @@ module ActionController
26
26
  included do
27
27
  class_attribute :etag_with_template_digest, default: true
28
28
 
29
- ActiveSupport.on_load :action_view, yield: true do
30
- etag do |options|
31
- determine_template_etag(options) if etag_with_template_digest
32
- end
29
+ etag do |options|
30
+ determine_template_etag(options) if etag_with_template_digest
33
31
  end
34
32
  end
35
33
 
@@ -23,6 +23,39 @@ module ActionController
23
23
  end
24
24
 
25
25
  class UrlGenerationError < ActionControllerError #:nodoc:
26
+ attr_reader :routes, :route_name, :method_name
27
+
28
+ def initialize(message, routes = nil, route_name = nil, method_name = nil)
29
+ @routes = routes
30
+ @route_name = route_name
31
+ @method_name = method_name
32
+
33
+ super(message)
34
+ end
35
+
36
+ class Correction
37
+ def initialize(error)
38
+ @error = error
39
+ end
40
+
41
+ def corrections
42
+ if @error.method_name
43
+ maybe_these = @error.routes.named_routes.helper_names.grep(/#{@error.route_name}/)
44
+ maybe_these -= [@error.method_name.to_s] # remove exact match
45
+
46
+ maybe_these.sort_by { |n|
47
+ DidYouMean::Jaro.distance(@error.route_name, n)
48
+ }.reverse.first(4)
49
+ else
50
+ []
51
+ end
52
+ end
53
+ end
54
+
55
+ # We may not have DYM, and DYM might not let us register error handlers
56
+ if defined?(DidYouMean) && DidYouMean.respond_to?(:correct_error)
57
+ DidYouMean.correct_error(self, Correction)
58
+ end
26
59
  end
27
60
 
28
61
  class MethodNotAllowed < ActionControllerError #:nodoc:
@@ -29,19 +29,22 @@ module ActionController
29
29
  content_type = options.delete(:content_type)
30
30
 
31
31
  options.each do |key, value|
32
- headers[key.to_s.dasherize.split("-").each { |v| v[0] = v[0].chr.upcase }.join("-")] = value.to_s
32
+ headers[key.to_s.split(/[-_]/).each { |v| v[0] = v[0].upcase }.join("-")] = value.to_s
33
33
  end
34
34
 
35
35
  self.status = status
36
36
  self.location = url_for(location) if location
37
37
 
38
- self.response_body = ""
39
-
40
38
  if include_content?(response_code)
41
- self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
39
+ unless self.media_type
40
+ self.content_type = content_type || (Mime[formats.first] if formats) || Mime[:html]
41
+ end
42
+
42
43
  response.charset = false
43
44
  end
44
45
 
46
+ self.response_body = ""
47
+
45
48
  true
46
49
  end
47
50
 
@@ -11,7 +11,12 @@ module ActionController
11
11
  #
12
12
  # In previous versions of \Rails the controller will include a helper which
13
13
  # matches the name of the controller, e.g., <tt>MyController</tt> will automatically
14
- # include <tt>MyHelper</tt>. To return old behavior set +config.action_controller.include_all_helpers+ to +false+.
14
+ # include <tt>MyHelper</tt>. You can revert to the old behavior with the following:
15
+ #
16
+ # # config/application.rb
17
+ # class Application < Rails::Application
18
+ # config.action_controller.include_all_helpers = false
19
+ # end
15
20
  #
16
21
  # Additional helpers can be specified using the +helper+ class method in ActionController::Base or any
17
22
  # controller which inherits from it.
@@ -73,6 +78,11 @@ module ActionController
73
78
  end
74
79
 
75
80
  # Provides a proxy to access helper methods from outside the view.
81
+ #
82
+ # Note that the proxy is rendered under a different view context.
83
+ # This may cause incorrect behaviour with capture methods. Consider
84
+ # using {helper}[rdoc-ref:AbstractController::Helpers::ClassMethods#helper]
85
+ # instead when using +capture+.
76
86
  def helpers
77
87
  @helper_proxy ||= begin
78
88
  proxy = ActionView::Base.empty
@@ -76,6 +76,8 @@ module ActionController
76
76
 
77
77
  def http_basic_authenticate_or_request_with(name:, password:, realm: nil, message: nil)
78
78
  authenticate_or_request_with_http_basic(realm, message) do |given_name, given_password|
79
+ # This comparison uses & so that it doesn't short circuit and
80
+ # uses `secure_compare` so that length information isn't leaked.
79
81
  ActiveSupport::SecurityUtils.secure_compare(given_name, name) &
80
82
  ActiveSupport::SecurityUtils.secure_compare(given_password, password)
81
83
  end
@@ -136,7 +138,7 @@ module ActionController
136
138
  #
137
139
  # === Simple \Digest example
138
140
  #
139
- # require 'digest/md5'
141
+ # require "digest/md5"
140
142
  # class PostsController < ApplicationController
141
143
  # REALM = "SuperSecret"
142
144
  # USERS = {"dhh" => "secret", #plain text password
@@ -482,7 +484,7 @@ module ActionController
482
484
  def raw_params(auth)
483
485
  _raw_params = auth.sub(TOKEN_REGEX, "").split(/\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
484
486
 
485
- if !(_raw_params.first =~ %r{\A#{TOKEN_KEY}})
487
+ if !_raw_params.first.start_with?(TOKEN_KEY)
486
488
  _raw_params[0] = "#{TOKEN_KEY}#{_raw_params.first}"
487
489
  end
488
490
 
@@ -22,7 +22,7 @@ module ActionController
22
22
  # Third, if we DON'T find a template AND the request is a page load in a web
23
23
  # browser (technically, a non-XHR GET request for an HTML response) where
24
24
  # you reasonably expect to have rendered a template, then we raise
25
- # <tt>ActionView::UnknownFormat</tt> with an explanation.
25
+ # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
26
26
  #
27
27
  # Finally, if we DON'T find a template AND the request isn't a browser page
28
28
  # load, then we implicitly respond with <tt>204 No Content</tt>.
@@ -16,10 +16,11 @@ module ActionController
16
16
 
17
17
  attr_internal :view_runtime
18
18
 
19
- def process_action(*args)
19
+ def process_action(*)
20
20
  raw_payload = {
21
21
  controller: self.class.name,
22
22
  action: action_name,
23
+ request: request,
23
24
  params: request.filtered_parameters,
24
25
  headers: request.headers,
25
26
  format: request.format.ref,
@@ -27,18 +28,19 @@ module ActionController
27
28
  path: request.fullpath
28
29
  }
29
30
 
30
- ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload.dup)
31
+ ActiveSupport::Notifications.instrument("start_processing.action_controller", raw_payload)
31
32
 
32
33
  ActiveSupport::Notifications.instrument("process_action.action_controller", raw_payload) do |payload|
33
- super.tap do
34
- payload[:status] = response.status
35
- end
34
+ result = super
35
+ payload[:response] = response
36
+ payload[:status] = response.status
37
+ result
36
38
  ensure
37
39
  append_info_to_payload(payload)
38
40
  end
39
41
  end
40
42
 
41
- def render(*args)
43
+ def render(*)
42
44
  render_output = nil
43
45
  self.view_runtime = cleanup_view_runtime do
44
46
  Benchmark.ms { render_output = super }
@@ -59,8 +61,8 @@ module ActionController
59
61
  end
60
62
  end
61
63
 
62
- def redirect_to(*args)
63
- ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
64
+ def redirect_to(*)
65
+ ActiveSupport::Notifications.instrument("redirect_to.action_controller", request: request) do |payload|
64
66
  result = super
65
67
  payload[:status] = response.status
66
68
  payload[:location] = response.filtered_location
@@ -70,7 +72,7 @@ module ActionController
70
72
 
71
73
  private
72
74
  # A hook invoked every time a before callback is halted.
73
- def halted_callback_hook(filter)
75
+ def halted_callback_hook(filter, _)
74
76
  ActiveSupport::Notifications.instrument("halted_callback.action_controller", filter: filter)
75
77
  end
76
78
 
@@ -136,11 +136,11 @@ module ActionController
136
136
  attr_accessor :ignore_disconnect
137
137
 
138
138
  def initialize(response)
139
+ super(response, SizedQueue.new(10))
139
140
  @error_callback = lambda { true }
140
141
  @cv = new_cond
141
142
  @aborted = false
142
143
  @ignore_disconnect = false
143
- super(response, SizedQueue.new(10))
144
144
  end
145
145
 
146
146
  def write(string)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActionController
4
+ module Logging
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # Set a different log level per request.
9
+ #
10
+ # # Use the debug log level if a particular cookie is set.
11
+ # class ApplicationController < ActionController::Base
12
+ # log_at :debug, if: -> { cookies[:debug] }
13
+ # end
14
+ #
15
+ def log_at(level, **options)
16
+ around_action ->(_, action) { logger.log_at(level, &action) }, **options
17
+ end
18
+ end
19
+ end
20
+ end
@@ -142,7 +142,7 @@ module ActionController #:nodoc:
142
142
  #
143
143
  # You can set the variant in a +before_action+:
144
144
  #
145
- # request.variant = :tablet if request.user_agent =~ /iPad/
145
+ # request.variant = :tablet if /iPad/.match?(request.user_agent)
146
146
  #
147
147
  # Respond to variants in the action just like you respond to formats:
148
148
  #
@@ -209,7 +209,7 @@ module ActionController #:nodoc:
209
209
  raise ActionController::RespondToMismatchError
210
210
  end
211
211
  _process_format(format)
212
- _set_rendered_content_type format
212
+ _set_rendered_content_type(format) unless collector.any_response?
213
213
  response = collector.response
214
214
  response.call if response
215
215
  else
@@ -268,6 +268,10 @@ module ActionController #:nodoc:
268
268
  end
269
269
  end
270
270
 
271
+ def any_response?
272
+ !@responses.fetch(format, false) && @responses[Mime::ALL]
273
+ end
274
+
271
275
  def response
272
276
  response = @responses.fetch(format, @responses[Mime::ALL])
273
277
  if response.is_a?(VariantCollector) # `format.html.phone` - variant inline syntax
@@ -12,11 +12,13 @@ module ActionController
12
12
  end
13
13
 
14
14
  def setup_param_encode # :nodoc:
15
- @_parameter_encodings = {}
15
+ @_parameter_encodings = Hash.new { |h, k| h[k] = {} }
16
16
  end
17
17
 
18
- def binary_params_for?(action) # :nodoc:
19
- @_parameter_encodings[action.to_s]
18
+ def action_encoding_template(action) # :nodoc:
19
+ if @_parameter_encodings.has_key?(action.to_s)
20
+ @_parameter_encodings[action.to_s]
21
+ end
20
22
  end
21
23
 
22
24
  # Specify that a given action's parameters should all be encoded as
@@ -44,7 +46,36 @@ module ActionController
44
46
  # encoded as ASCII-8BIT. This is useful in the case where an application
45
47
  # must handle data but encoding of the data is unknown, like file system data.
46
48
  def skip_parameter_encoding(action)
47
- @_parameter_encodings[action.to_s] = true
49
+ @_parameter_encodings[action.to_s] = Hash.new { Encoding::ASCII_8BIT }
50
+ end
51
+
52
+ # Specify the encoding for a parameter on an action.
53
+ # If not specified the default is UTF-8.
54
+ #
55
+ # You can specify a binary (ASCII_8BIT) parameter with:
56
+ #
57
+ # class RepositoryController < ActionController::Base
58
+ # # This specifies that file_path is not UTF-8 and is instead ASCII_8BIT
59
+ # param_encoding :show, :file_path, Encoding::ASCII_8BIT
60
+ #
61
+ # def show
62
+ # @repo = Repository.find_by_filesystem_path params[:file_path]
63
+ #
64
+ # # params[:repo_name] remains UTF-8 encoded
65
+ # @repo_name = params[:repo_name]
66
+ # end
67
+ #
68
+ # def index
69
+ # @repositories = Repository.all
70
+ # end
71
+ # end
72
+ #
73
+ # The file_path parameter on the show action would be encoded as ASCII-8BIT,
74
+ # but all other arguments will remain UTF-8 encoded.
75
+ # This is useful in the case where an application must handle data
76
+ # but encoding of the data is unknown, like file system data.
77
+ def param_encoding(action, param, encoding)
78
+ @_parameter_encodings[action.to_s][param.to_s] = encoding
48
79
  end
49
80
  end
50
81
  end