actionpack 7.1.3 → 7.2.1.1

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 (158) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +82 -501
  3. data/lib/abstract_controller/asset_paths.rb +2 -0
  4. data/lib/abstract_controller/base.rb +102 -98
  5. data/lib/abstract_controller/caching/fragments.rb +50 -53
  6. data/lib/abstract_controller/caching.rb +2 -0
  7. data/lib/abstract_controller/callbacks.rb +66 -64
  8. data/lib/abstract_controller/collector.rb +6 -6
  9. data/lib/abstract_controller/deprecator.rb +2 -0
  10. data/lib/abstract_controller/error.rb +2 -0
  11. data/lib/abstract_controller/helpers.rb +70 -85
  12. data/lib/abstract_controller/logger.rb +2 -0
  13. data/lib/abstract_controller/railties/routes_helpers.rb +2 -0
  14. data/lib/abstract_controller/rendering.rb +13 -12
  15. data/lib/abstract_controller/translation.rb +15 -7
  16. data/lib/abstract_controller/url_for.rb +8 -6
  17. data/lib/abstract_controller.rb +2 -0
  18. data/lib/action_controller/api/api_rendering.rb +2 -0
  19. data/lib/action_controller/api.rb +74 -72
  20. data/lib/action_controller/base.rb +198 -126
  21. data/lib/action_controller/caching.rb +15 -12
  22. data/lib/action_controller/deprecator.rb +2 -0
  23. data/lib/action_controller/form_builder.rb +20 -17
  24. data/lib/action_controller/log_subscriber.rb +3 -1
  25. data/lib/action_controller/metal/allow_browser.rb +123 -0
  26. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  27. data/lib/action_controller/metal/conditional_get.rb +188 -174
  28. data/lib/action_controller/metal/content_security_policy.rb +25 -24
  29. data/lib/action_controller/metal/cookies.rb +4 -2
  30. data/lib/action_controller/metal/data_streaming.rb +64 -55
  31. data/lib/action_controller/metal/default_headers.rb +5 -3
  32. data/lib/action_controller/metal/etag_with_flash.rb +3 -1
  33. data/lib/action_controller/metal/etag_with_template_digest.rb +17 -15
  34. data/lib/action_controller/metal/exceptions.rb +11 -9
  35. data/lib/action_controller/metal/flash.rb +12 -10
  36. data/lib/action_controller/metal/head.rb +12 -10
  37. data/lib/action_controller/metal/helpers.rb +63 -55
  38. data/lib/action_controller/metal/http_authentication.rb +210 -205
  39. data/lib/action_controller/metal/implicit_render.rb +17 -15
  40. data/lib/action_controller/metal/instrumentation.rb +15 -12
  41. data/lib/action_controller/metal/live.rb +113 -107
  42. data/lib/action_controller/metal/logging.rb +6 -4
  43. data/lib/action_controller/metal/mime_responds.rb +151 -142
  44. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  45. data/lib/action_controller/metal/params_wrapper.rb +57 -59
  46. data/lib/action_controller/metal/permissions_policy.rb +13 -12
  47. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  48. data/lib/action_controller/metal/redirecting.rb +108 -82
  49. data/lib/action_controller/metal/renderers.rb +50 -49
  50. data/lib/action_controller/metal/rendering.rb +103 -75
  51. data/lib/action_controller/metal/request_forgery_protection.rb +162 -133
  52. data/lib/action_controller/metal/rescue.rb +11 -9
  53. data/lib/action_controller/metal/streaming.rb +138 -136
  54. data/lib/action_controller/metal/strong_parameters.rb +525 -480
  55. data/lib/action_controller/metal/testing.rb +2 -0
  56. data/lib/action_controller/metal/url_for.rb +17 -15
  57. data/lib/action_controller/metal.rb +86 -60
  58. data/lib/action_controller/railtie.rb +3 -0
  59. data/lib/action_controller/railties/helpers.rb +2 -0
  60. data/lib/action_controller/renderer.rb +42 -36
  61. data/lib/action_controller/template_assertions.rb +4 -2
  62. data/lib/action_controller/test_case.rb +146 -126
  63. data/lib/action_controller.rb +10 -3
  64. data/lib/action_dispatch/constants.rb +2 -0
  65. data/lib/action_dispatch/deprecator.rb +2 -0
  66. data/lib/action_dispatch/http/cache.rb +27 -26
  67. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  68. data/lib/action_dispatch/http/content_security_policy.rb +44 -38
  69. data/lib/action_dispatch/http/filter_parameters.rb +18 -9
  70. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  71. data/lib/action_dispatch/http/headers.rb +22 -22
  72. data/lib/action_dispatch/http/mime_negotiation.rb +30 -41
  73. data/lib/action_dispatch/http/mime_type.rb +31 -24
  74. data/lib/action_dispatch/http/mime_types.rb +2 -0
  75. data/lib/action_dispatch/http/parameters.rb +11 -9
  76. data/lib/action_dispatch/http/permissions_policy.rb +20 -44
  77. data/lib/action_dispatch/http/rack_cache.rb +2 -0
  78. data/lib/action_dispatch/http/request.rb +94 -75
  79. data/lib/action_dispatch/http/response.rb +73 -61
  80. data/lib/action_dispatch/http/upload.rb +18 -16
  81. data/lib/action_dispatch/http/url.rb +75 -73
  82. data/lib/action_dispatch/journey/formatter.rb +13 -6
  83. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  84. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  85. data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
  86. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  87. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  88. data/lib/action_dispatch/journey/parser.rb +4 -3
  89. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  90. data/lib/action_dispatch/journey/path/pattern.rb +4 -1
  91. data/lib/action_dispatch/journey/route.rb +9 -7
  92. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  93. data/lib/action_dispatch/journey/router.rb +4 -2
  94. data/lib/action_dispatch/journey/routes.rb +4 -2
  95. data/lib/action_dispatch/journey/scanner.rb +4 -2
  96. data/lib/action_dispatch/journey/visitors.rb +2 -0
  97. data/lib/action_dispatch/journey.rb +2 -0
  98. data/lib/action_dispatch/log_subscriber.rb +2 -0
  99. data/lib/action_dispatch/middleware/actionable_exceptions.rb +2 -0
  100. data/lib/action_dispatch/middleware/assume_ssl.rb +8 -5
  101. data/lib/action_dispatch/middleware/callbacks.rb +3 -1
  102. data/lib/action_dispatch/middleware/cookies.rb +119 -104
  103. data/lib/action_dispatch/middleware/debug_exceptions.rb +13 -5
  104. data/lib/action_dispatch/middleware/debug_locks.rb +15 -13
  105. data/lib/action_dispatch/middleware/debug_view.rb +2 -0
  106. data/lib/action_dispatch/middleware/exception_wrapper.rb +6 -11
  107. data/lib/action_dispatch/middleware/executor.rb +8 -0
  108. data/lib/action_dispatch/middleware/flash.rb +63 -51
  109. data/lib/action_dispatch/middleware/host_authorization.rb +17 -15
  110. data/lib/action_dispatch/middleware/public_exceptions.rb +8 -6
  111. data/lib/action_dispatch/middleware/reloader.rb +5 -3
  112. data/lib/action_dispatch/middleware/remote_ip.rb +77 -72
  113. data/lib/action_dispatch/middleware/request_id.rb +14 -9
  114. data/lib/action_dispatch/middleware/server_timing.rb +4 -2
  115. data/lib/action_dispatch/middleware/session/abstract_store.rb +2 -0
  116. data/lib/action_dispatch/middleware/session/cache_store.rb +13 -8
  117. data/lib/action_dispatch/middleware/session/cookie_store.rb +27 -26
  118. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +7 -3
  119. data/lib/action_dispatch/middleware/show_exceptions.rb +31 -21
  120. data/lib/action_dispatch/middleware/ssl.rb +43 -40
  121. data/lib/action_dispatch/middleware/stack.rb +11 -10
  122. data/lib/action_dispatch/middleware/static.rb +33 -31
  123. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +1 -1
  124. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +1 -1
  125. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +1 -1
  126. data/lib/action_dispatch/railtie.rb +2 -4
  127. data/lib/action_dispatch/request/session.rb +23 -21
  128. data/lib/action_dispatch/request/utils.rb +2 -0
  129. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  130. data/lib/action_dispatch/routing/inspector.rb +5 -3
  131. data/lib/action_dispatch/routing/mapper.rb +671 -636
  132. data/lib/action_dispatch/routing/polymorphic_routes.rb +69 -62
  133. data/lib/action_dispatch/routing/redirection.rb +37 -32
  134. data/lib/action_dispatch/routing/route_set.rb +59 -45
  135. data/lib/action_dispatch/routing/routes_proxy.rb +6 -4
  136. data/lib/action_dispatch/routing/url_for.rb +130 -125
  137. data/lib/action_dispatch/routing.rb +150 -148
  138. data/lib/action_dispatch/system_test_case.rb +91 -81
  139. data/lib/action_dispatch/system_testing/browser.rb +10 -3
  140. data/lib/action_dispatch/system_testing/driver.rb +3 -1
  141. data/lib/action_dispatch/system_testing/server.rb +2 -0
  142. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +32 -21
  143. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  144. data/lib/action_dispatch/testing/assertion_response.rb +8 -6
  145. data/lib/action_dispatch/testing/assertions/response.rb +26 -23
  146. data/lib/action_dispatch/testing/assertions/routing.rb +153 -84
  147. data/lib/action_dispatch/testing/assertions.rb +2 -0
  148. data/lib/action_dispatch/testing/integration.rb +223 -222
  149. data/lib/action_dispatch/testing/request_encoder.rb +2 -0
  150. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  151. data/lib/action_dispatch/testing/test_process.rb +12 -8
  152. data/lib/action_dispatch/testing/test_request.rb +3 -1
  153. data/lib/action_dispatch/testing/test_response.rb +27 -26
  154. data/lib/action_dispatch.rb +22 -28
  155. data/lib/action_pack/gem_version.rb +6 -4
  156. data/lib/action_pack/version.rb +3 -1
  157. data/lib/action_pack.rb +17 -16
  158. metadata +39 -16
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :markup: markdown
4
+
5
+ module ActionController # :nodoc:
6
+ module AllowBrowser
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ # Specify the browser versions that will be allowed to access all actions (or
11
+ # some, as limited by `only:` or `except:`). Only browsers matched in the hash
12
+ # or named set passed to `versions:` will be blocked if they're below the
13
+ # versions specified. This means that all other browsers, as well as agents that
14
+ # aren't reporting a user-agent header, will be allowed access.
15
+ #
16
+ # A browser that's blocked will by default be served the file in
17
+ # public/406-unsupported-browser.html with a HTTP status code of "406 Not
18
+ # Acceptable".
19
+ #
20
+ # In addition to specifically named browser versions, you can also pass
21
+ # `:modern` as the set to restrict support to browsers natively supporting webp
22
+ # images, web push, badges, import maps, CSS nesting, and CSS :has. This
23
+ # includes Safari 17.2+, Chrome 120+, Firefox 121+, Opera 106+.
24
+ #
25
+ # You can use https://caniuse.com to check for browser versions supporting the
26
+ # features you use.
27
+ #
28
+ # You can use `ActiveSupport::Notifications` to subscribe to events of browsers
29
+ # being blocked using the `browser_block.action_controller` event name.
30
+ #
31
+ # Examples:
32
+ #
33
+ # class ApplicationController < ActionController::Base
34
+ # # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting, and CSS :has
35
+ # allow_browser versions: :modern
36
+ # end
37
+ #
38
+ # class ApplicationController < ActionController::Base
39
+ # # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
40
+ # allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
41
+ # end
42
+ #
43
+ # class MessagesController < ApplicationController
44
+ # # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
45
+ # allow_browser versions: { opera: 104, chrome: 119 }, only: :show
46
+ # end
47
+ def allow_browser(versions:, block: -> { render file: Rails.root.join("public/406-unsupported-browser.html"), layout: false, status: :not_acceptable }, **options)
48
+ before_action -> { allow_browser(versions: versions, block: block) }, **options
49
+ end
50
+ end
51
+
52
+ private
53
+ def allow_browser(versions:, block:)
54
+ require "useragent"
55
+
56
+ if BrowserBlocker.new(request, versions: versions).blocked?
57
+ ActiveSupport::Notifications.instrument("browser_block.action_controller", request: request, versions: versions) do
58
+ instance_exec(&block)
59
+ end
60
+ end
61
+ end
62
+
63
+ class BrowserBlocker
64
+ SETS = {
65
+ modern: { safari: 17.2, chrome: 120, firefox: 121, opera: 106, ie: false }
66
+ }
67
+
68
+ attr_reader :request, :versions
69
+
70
+ def initialize(request, versions:)
71
+ @request, @versions = request, versions
72
+ end
73
+
74
+ def blocked?
75
+ user_agent_version_reported? && unsupported_browser?
76
+ end
77
+
78
+ private
79
+ def parsed_user_agent
80
+ @parsed_user_agent ||= UserAgent.parse(request.user_agent)
81
+ end
82
+
83
+ def user_agent_version_reported?
84
+ request.user_agent.present? && parsed_user_agent.version.to_s.present?
85
+ end
86
+
87
+ def unsupported_browser?
88
+ version_guarded_browser? && version_below_minimum_required? && !bot?
89
+ end
90
+
91
+ def version_guarded_browser?
92
+ minimum_browser_version_for_browser != nil
93
+ end
94
+
95
+ def bot?
96
+ parsed_user_agent.bot?
97
+ end
98
+
99
+ def version_below_minimum_required?
100
+ if minimum_browser_version_for_browser
101
+ parsed_user_agent.version < UserAgent::Version.new(minimum_browser_version_for_browser.to_s)
102
+ else
103
+ true
104
+ end
105
+ end
106
+
107
+ def minimum_browser_version_for_browser
108
+ expanded_versions[normalized_browser_name]
109
+ end
110
+
111
+ def expanded_versions
112
+ @expanded_versions ||= (SETS[versions] || versions).with_indifferent_access
113
+ end
114
+
115
+ def normalized_browser_name
116
+ case name = parsed_user_agent.browser.downcase
117
+ when "internet explorer" then "ie"
118
+ else name
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  module ActionController
4
6
  module BasicImplicitRender # :nodoc:
5
7
  def send_action(method, *args)
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # :markup: markdown
4
+
3
5
  require "active_support/core_ext/object/try"
4
6
  require "active_support/core_ext/integer/time"
5
7
 
@@ -14,116 +16,124 @@ module ActionController
14
16
  end
15
17
 
16
18
  module ClassMethods
17
- # Allows you to consider additional controller-wide information when generating an ETag.
18
- # For example, if you serve pages tailored depending on who's logged in at the moment, you
19
- # may want to add the current user id to be part of the ETag to prevent unauthorized displaying
20
- # of cached pages.
19
+ # Allows you to consider additional controller-wide information when generating
20
+ # an ETag. For example, if you serve pages tailored depending on who's logged in
21
+ # at the moment, you may want to add the current user id to be part of the ETag
22
+ # to prevent unauthorized displaying of cached pages.
21
23
  #
22
- # class InvoicesController < ApplicationController
23
- # etag { current_user&.id }
24
+ # class InvoicesController < ApplicationController
25
+ # etag { current_user&.id }
24
26
  #
25
- # def show
26
- # # Etag will differ even for the same invoice when it's viewed by a different current_user
27
- # @invoice = Invoice.find(params[:id])
28
- # fresh_when etag: @invoice
27
+ # def show
28
+ # # Etag will differ even for the same invoice when it's viewed by a different current_user
29
+ # @invoice = Invoice.find(params[:id])
30
+ # fresh_when etag: @invoice
31
+ # end
29
32
  # end
30
- # end
31
33
  def etag(&etagger)
32
34
  self.etaggers += [etagger]
33
35
  end
34
36
  end
35
37
 
36
- # Sets the +etag+, +last_modified+, or both on the response, and renders a
37
- # <tt>304 Not Modified</tt> response if the request is already fresh.
38
- #
39
- # ==== Options
40
- #
41
- # [+:etag+]
42
- # Sets a "weak" ETag validator on the response. See the +:weak_etag+ option.
43
- # [+:weak_etag+]
44
- # Sets a "weak" ETag validator on the response. Requests that specify an
45
- # +If-None-Match+ header may receive a <tt>304 Not Modified</tt> response
46
- # if the ETag matches exactly.
47
- #
48
- # A weak ETag indicates semantic equivalence, not byte-for-byte equality,
49
- # so they're good for caching HTML pages in browser caches. They can't be
50
- # used for responses that must be byte-identical, like serving +Range+
51
- # requests within a PDF file.
52
- # [+:strong_etag+]
53
- # Sets a "strong" ETag validator on the response. Requests that specify an
54
- # +If-None-Match+ header may receive a <tt>304 Not Modified</tt> response
55
- # if the ETag matches exactly.
56
- #
57
- # A strong ETag implies exact equality -- the response must match byte for
58
- # byte. This is necessary for serving +Range+ requests within a large
59
- # video or PDF file, for example, or for compatibility with some CDNs that
60
- # don't support weak ETags.
61
- # [+:last_modified+]
62
- # Sets a "weak" last-update validator on the response. Subsequent requests
63
- # that specify an +If-Modified-Since+ header may receive a <tt>304 Not Modified</tt>
64
- # response if +last_modified+ <= +If-Modified-Since+.
65
- # [+:public+]
66
- # By default the +Cache-Control+ header is private. Set this option to
67
- # +true+ if you want your application to be cacheable by other devices,
68
- # such as proxy caches.
69
- # [+:cache_control+]
70
- # When given, will overwrite an existing +Cache-Control+ header. For a
71
- # list of +Cache-Control+ directives, see the {article on
72
- # MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control].
73
- # [+:template+]
74
- # By default, the template digest for the current controller/action is
75
- # included in ETags. If the action renders a different template, you can
76
- # include its digest instead. If the action doesn't render a template at
77
- # all, you can pass <tt>template: false</tt> to skip any attempt to check
78
- # for a template digest.
79
- #
80
- # ==== Examples
81
- #
82
- # def show
83
- # @article = Article.find(params[:id])
84
- # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
85
- # end
86
- #
87
- # This will send a <tt>304 Not Modified</tt> response if the request
88
- # specifies a matching ETag and +If-Modified-Since+ header. Otherwise, it
89
- # will render the +show+ template.
38
+ # Sets the `etag`, `last_modified`, or both on the response, and renders a `304
39
+ # Not Modified` response if the request is already fresh.
40
+ #
41
+ # #### Options
42
+ #
43
+ # `:etag`
44
+ # : Sets a "weak" ETag validator on the response. See the `:weak_etag` option.
45
+ #
46
+ # `:weak_etag`
47
+ # : Sets a "weak" ETag validator on the response. Requests that specify an
48
+ # `If-None-Match` header may receive a `304 Not Modified` response if the
49
+ # ETag matches exactly.
50
+ #
51
+ # : A weak ETag indicates semantic equivalence, not byte-for-byte equality, so
52
+ # they're good for caching HTML pages in browser caches. They can't be used
53
+ # for responses that must be byte-identical, like serving `Range` requests
54
+ # within a PDF file.
55
+ #
56
+ # `:strong_etag`
57
+ # : Sets a "strong" ETag validator on the response. Requests that specify an
58
+ # `If-None-Match` header may receive a `304 Not Modified` response if the
59
+ # ETag matches exactly.
60
+ #
61
+ # : A strong ETag implies exact equality -- the response must match byte for
62
+ # byte. This is necessary for serving `Range` requests within a large video
63
+ # or PDF file, for example, or for compatibility with some CDNs that don't
64
+ # support weak ETags.
65
+ #
66
+ # `:last_modified`
67
+ # : Sets a "weak" last-update validator on the response. Subsequent requests
68
+ # that specify an `If-Modified-Since` header may receive a `304 Not
69
+ # Modified` response if `last_modified` <= `If-Modified-Since`.
70
+ #
71
+ # `:public`
72
+ # : By default the `Cache-Control` header is private. Set this option to
73
+ # `true` if you want your application to be cacheable by other devices, such
74
+ # as proxy caches.
75
+ #
76
+ # `:cache_control`
77
+ # : When given, will overwrite an existing `Cache-Control` header. For a list
78
+ # of `Cache-Control` directives, see the [article on
79
+ # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Contr
80
+ # ol).
81
+ #
82
+ # `:template`
83
+ # : By default, the template digest for the current controller/action is
84
+ # included in ETags. If the action renders a different template, you can
85
+ # include its digest instead. If the action doesn't render a template at
86
+ # all, you can pass `template: false` to skip any attempt to check for a
87
+ # template digest.
88
+ #
89
+ #
90
+ # #### Examples
91
+ #
92
+ # def show
93
+ # @article = Article.find(params[:id])
94
+ # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
95
+ # end
96
+ #
97
+ # This will send a `304 Not Modified` response if the request specifies a
98
+ # matching ETag and `If-Modified-Since` header. Otherwise, it will render the
99
+ # `show` template.
90
100
  #
91
101
  # You can also just pass a record:
92
102
  #
93
- # def show
94
- # @article = Article.find(params[:id])
95
- # fresh_when(@article)
96
- # end
103
+ # def show
104
+ # @article = Article.find(params[:id])
105
+ # fresh_when(@article)
106
+ # end
97
107
  #
98
- # +etag+ will be set to the record, and +last_modified+ will be set to the
99
- # record's +updated_at+.
108
+ # `etag` will be set to the record, and `last_modified` will be set to the
109
+ # record's `updated_at`.
100
110
  #
101
- # You can also pass an object that responds to +maximum+, such as a
102
- # collection of records:
111
+ # You can also pass an object that responds to `maximum`, such as a collection
112
+ # of records:
103
113
  #
104
- # def index
105
- # @articles = Article.all
106
- # fresh_when(@articles)
107
- # end
114
+ # def index
115
+ # @articles = Article.all
116
+ # fresh_when(@articles)
117
+ # end
108
118
  #
109
- # In this case, +etag+ will be set to the collection, and +last_modified+
110
- # will be set to <tt>maximum(:updated_at)</tt> (the timestamp of the most
111
- # recently updated record).
119
+ # In this case, `etag` will be set to the collection, and `last_modified` will
120
+ # be set to `maximum(:updated_at)` (the timestamp of the most recently updated
121
+ # record).
112
122
  #
113
- # When passing a record or a collection, you can still specify other
114
- # options, such as +:public+ and +:cache_control+:
123
+ # When passing a record or a collection, you can still specify other options,
124
+ # such as `:public` and `:cache_control`:
115
125
  #
116
- # def show
117
- # @article = Article.find(params[:id])
118
- # fresh_when(@article, public: true, cache_control: { no_cache: true })
119
- # end
126
+ # def show
127
+ # @article = Article.find(params[:id])
128
+ # fresh_when(@article, public: true, cache_control: { no_cache: true })
129
+ # end
120
130
  #
121
- # The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
131
+ # The above will set `Cache-Control: public, no-cache` in the response.
122
132
  #
123
133
  # When rendering a different template than the controller/action's default
124
134
  # template, you can indicate which digest to include in the ETag:
125
135
  #
126
- # before_action { fresh_when @article, template: "widgets/show" }
136
+ # before_action { fresh_when @article, template: "widgets/show" }
127
137
  #
128
138
  def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
129
139
  response.cache_control.delete(:no_store)
@@ -145,131 +155,135 @@ module ActionController
145
155
  head :not_modified if request.fresh?(response)
146
156
  end
147
157
 
148
- # Sets the +etag+ and/or +last_modified+ on the response and checks them
149
- # against the request. If the request doesn't match the provided options, it
150
- # is considered stale, and the response should be rendered from scratch.
151
- # Otherwise, it is fresh, and a <tt>304 Not Modified</tt> is sent.
158
+ # Sets the `etag` and/or `last_modified` on the response and checks them against
159
+ # the request. If the request doesn't match the provided options, it is
160
+ # considered stale, and the response should be rendered from scratch. Otherwise,
161
+ # it is fresh, and a `304 Not Modified` is sent.
152
162
  #
153
- # ==== Options
163
+ # #### Options
154
164
  #
155
165
  # See #fresh_when for supported options.
156
166
  #
157
- # ==== Examples
167
+ # #### Examples
158
168
  #
159
- # def show
160
- # @article = Article.find(params[:id])
169
+ # def show
170
+ # @article = Article.find(params[:id])
161
171
  #
162
- # if stale?(etag: @article, last_modified: @article.updated_at)
163
- # @statistics = @article.really_expensive_call
164
- # respond_to do |format|
165
- # # all the supported formats
172
+ # if stale?(etag: @article, last_modified: @article.updated_at)
173
+ # @statistics = @article.really_expensive_call
174
+ # respond_to do |format|
175
+ # # all the supported formats
176
+ # end
166
177
  # end
167
178
  # end
168
- # end
169
179
  #
170
180
  # You can also just pass a record:
171
181
  #
172
- # def show
173
- # @article = Article.find(params[:id])
182
+ # def show
183
+ # @article = Article.find(params[:id])
174
184
  #
175
- # if stale?(@article)
176
- # @statistics = @article.really_expensive_call
177
- # respond_to do |format|
178
- # # all the supported formats
185
+ # if stale?(@article)
186
+ # @statistics = @article.really_expensive_call
187
+ # respond_to do |format|
188
+ # # all the supported formats
189
+ # end
179
190
  # end
180
191
  # end
181
- # end
182
192
  #
183
- # +etag+ will be set to the record, and +last_modified+ will be set to the
184
- # record's +updated_at+.
193
+ # `etag` will be set to the record, and `last_modified` will be set to the
194
+ # record's `updated_at`.
185
195
  #
186
- # You can also pass an object that responds to +maximum+, such as a
187
- # collection of records:
196
+ # You can also pass an object that responds to `maximum`, such as a collection
197
+ # of records:
188
198
  #
189
- # def index
190
- # @articles = Article.all
199
+ # def index
200
+ # @articles = Article.all
191
201
  #
192
- # if stale?(@articles)
193
- # @statistics = @articles.really_expensive_call
194
- # respond_to do |format|
195
- # # all the supported formats
202
+ # if stale?(@articles)
203
+ # @statistics = @articles.really_expensive_call
204
+ # respond_to do |format|
205
+ # # all the supported formats
206
+ # end
196
207
  # end
197
208
  # end
198
- # end
199
209
  #
200
- # In this case, +etag+ will be set to the collection, and +last_modified+
201
- # will be set to <tt>maximum(:updated_at)</tt> (the timestamp of the most
202
- # recently updated record).
210
+ # In this case, `etag` will be set to the collection, and `last_modified` will
211
+ # be set to `maximum(:updated_at)` (the timestamp of the most recently updated
212
+ # record).
203
213
  #
204
- # When passing a record or a collection, you can still specify other
205
- # options, such as +:public+ and +:cache_control+:
214
+ # When passing a record or a collection, you can still specify other options,
215
+ # such as `:public` and `:cache_control`:
206
216
  #
207
- # def show
208
- # @article = Article.find(params[:id])
217
+ # def show
218
+ # @article = Article.find(params[:id])
209
219
  #
210
- # if stale?(@article, public: true, cache_control: { no_cache: true })
211
- # @statistics = @articles.really_expensive_call
212
- # respond_to do |format|
213
- # # all the supported formats
220
+ # if stale?(@article, public: true, cache_control: { no_cache: true })
221
+ # @statistics = @articles.really_expensive_call
222
+ # respond_to do |format|
223
+ # # all the supported formats
224
+ # end
214
225
  # end
215
226
  # end
216
- # end
217
227
  #
218
- # The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
228
+ # The above will set `Cache-Control: public, no-cache` in the response.
219
229
  #
220
230
  # When rendering a different template than the controller/action's default
221
231
  # template, you can indicate which digest to include in the ETag:
222
232
  #
223
- # def show
224
- # super if stale?(@article, template: "widgets/show")
225
- # end
233
+ # def show
234
+ # super if stale?(@article, template: "widgets/show")
235
+ # end
226
236
  #
227
237
  def stale?(object = nil, **freshness_kwargs)
228
238
  fresh_when(object, **freshness_kwargs)
229
239
  !request.fresh?(response)
230
240
  end
231
241
 
232
- # Sets the +Cache-Control+ header, overwriting existing directives. This
233
- # method will also ensure an HTTP +Date+ header for client compatibility.
242
+ # Sets the `Cache-Control` header, overwriting existing directives. This method
243
+ # will also ensure an HTTP `Date` header for client compatibility.
244
+ #
245
+ # Defaults to issuing the `private` directive, so that intermediate caches must
246
+ # not cache the response.
247
+ #
248
+ # #### Options
249
+ #
250
+ # `:public`
251
+ # : If true, replaces the default `private` directive with the `public`
252
+ # directive.
253
+ #
254
+ # `:must_revalidate`
255
+ # : If true, adds the `must-revalidate` directive.
234
256
  #
235
- # Defaults to issuing the +private+ directive, so that intermediate caches
236
- # must not cache the response.
257
+ # `:stale_while_revalidate`
258
+ # : Sets the value of the `stale-while-revalidate` directive.
237
259
  #
238
- # ==== Options
260
+ # `:stale_if_error`
261
+ # : Sets the value of the `stale-if-error` directive.
239
262
  #
240
- # [+:public+]
241
- # If true, replaces the default +private+ directive with the +public+
242
- # directive.
243
- # [+:must_revalidate+]
244
- # If true, adds the +must-revalidate+ directive.
245
- # [+:stale_while_revalidate+]
246
- # Sets the value of the +stale-while-revalidate+ directive.
247
- # [+:stale_if_error+]
248
- # Sets the value of the +stale-if-error+ directive.
249
263
  #
250
- # Any additional key-value pairs are concatenated as directives. For a list
251
- # of supported +Cache-Control+ directives, see the {article on
252
- # MDN}[https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control].
264
+ # Any additional key-value pairs are concatenated as directives. For a list of
265
+ # supported `Cache-Control` directives, see the [article on
266
+ # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
253
267
  #
254
- # ==== Examples
268
+ # #### Examples
255
269
  #
256
- # expires_in 10.minutes
257
- # # => Cache-Control: max-age=600, private
270
+ # expires_in 10.minutes
271
+ # # => Cache-Control: max-age=600, private
258
272
  #
259
- # expires_in 10.minutes, public: true
260
- # # => Cache-Control: max-age=600, public
273
+ # expires_in 10.minutes, public: true
274
+ # # => Cache-Control: max-age=600, public
261
275
  #
262
- # expires_in 10.minutes, public: true, must_revalidate: true
263
- # # => Cache-Control: max-age=600, public, must-revalidate
276
+ # expires_in 10.minutes, public: true, must_revalidate: true
277
+ # # => Cache-Control: max-age=600, public, must-revalidate
264
278
  #
265
- # expires_in 1.hour, stale_while_revalidate: 60.seconds
266
- # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
279
+ # expires_in 1.hour, stale_while_revalidate: 60.seconds
280
+ # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
267
281
  #
268
- # expires_in 1.hour, stale_if_error: 5.minutes
269
- # # => Cache-Control: max-age=3600, private, stale-if-error=300
282
+ # expires_in 1.hour, stale_if_error: 5.minutes
283
+ # # => Cache-Control: max-age=3600, private, stale-if-error=300
270
284
  #
271
- # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
272
- # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
285
+ # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
286
+ # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
273
287
  #
274
288
  def expires_in(seconds, options = {})
275
289
  response.cache_control.delete(:no_store)
@@ -286,8 +300,8 @@ module ActionController
286
300
  response.date = Time.now unless response.date?
287
301
  end
288
302
 
289
- # Sets an HTTP 1.1 +Cache-Control+ header of <tt>no-cache</tt>. This means the
290
- # resource will be marked as stale, so clients must always revalidate.
303
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
304
+ # will be marked as stale, so clients must always revalidate.
291
305
  # Intermediate/browser caches may still store the asset.
292
306
  def expires_now
293
307
  response.cache_control.replace(no_cache: true)
@@ -295,12 +309,12 @@ module ActionController
295
309
 
296
310
  # Cache or yield the block. The cache is supposed to never expire.
297
311
  #
298
- # You can use this method when you have an HTTP response that never changes,
299
- # and the browser and proxies should cache it indefinitely.
312
+ # You can use this method when you have an HTTP response that never changes, and
313
+ # the browser and proxies should cache it indefinitely.
300
314
  #
301
- # * +public+: By default, HTTP responses are private, cached only on the
302
- # user's web browser. To allow proxies to cache the response, set +true+ to
303
- # indicate that they can serve the cached response to all users.
315
+ # * `public`: By default, HTTP responses are private, cached only on the
316
+ # user's web browser. To allow proxies to cache the response, set `true` to
317
+ # indicate that they can serve the cached response to all users.
304
318
  def http_cache_forever(public: false)
305
319
  expires_in 100.years, public: public
306
320
 
@@ -309,8 +323,8 @@ module ActionController
309
323
  public: public)
310
324
  end
311
325
 
312
- # Sets an HTTP 1.1 +Cache-Control+ header of <tt>no-store</tt>. This means the
313
- # resource may not be stored in any cache.
326
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
327
+ # may not be stored in any cache.
314
328
  def no_store
315
329
  response.cache_control.replace(no_store: true)
316
330
  end