actionpack 7.0.8.7 → 7.2.2.1

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 (171) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +90 -537
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +2 -2
  5. data/lib/abstract_controller/asset_paths.rb +2 -0
  6. data/lib/abstract_controller/base.rb +119 -106
  7. data/lib/abstract_controller/caching/fragments.rb +51 -52
  8. data/lib/abstract_controller/caching.rb +2 -0
  9. data/lib/abstract_controller/callbacks.rb +94 -67
  10. data/lib/abstract_controller/collector.rb +6 -6
  11. data/lib/abstract_controller/deprecator.rb +9 -0
  12. data/lib/abstract_controller/error.rb +2 -0
  13. data/lib/abstract_controller/helpers.rb +121 -91
  14. data/lib/abstract_controller/logger.rb +2 -0
  15. data/lib/abstract_controller/railties/routes_helpers.rb +3 -16
  16. data/lib/abstract_controller/rendering.rb +14 -13
  17. data/lib/abstract_controller/translation.rb +12 -30
  18. data/lib/abstract_controller/url_for.rb +9 -5
  19. data/lib/abstract_controller.rb +8 -0
  20. data/lib/action_controller/api/api_rendering.rb +2 -0
  21. data/lib/action_controller/api.rb +78 -73
  22. data/lib/action_controller/base.rb +199 -141
  23. data/lib/action_controller/caching.rb +16 -11
  24. data/lib/action_controller/deprecator.rb +9 -0
  25. data/lib/action_controller/form_builder.rb +21 -16
  26. data/lib/action_controller/log_subscriber.rb +19 -5
  27. data/lib/action_controller/metal/allow_browser.rb +123 -0
  28. data/lib/action_controller/metal/basic_implicit_render.rb +2 -0
  29. data/lib/action_controller/metal/conditional_get.rb +187 -174
  30. data/lib/action_controller/metal/content_security_policy.rb +26 -25
  31. data/lib/action_controller/metal/cookies.rb +4 -2
  32. data/lib/action_controller/metal/data_streaming.rb +65 -54
  33. data/lib/action_controller/metal/default_headers.rb +6 -2
  34. data/lib/action_controller/metal/etag_with_flash.rb +4 -0
  35. data/lib/action_controller/metal/etag_with_template_digest.rb +18 -14
  36. data/lib/action_controller/metal/exceptions.rb +19 -9
  37. data/lib/action_controller/metal/flash.rb +12 -10
  38. data/lib/action_controller/metal/head.rb +20 -16
  39. data/lib/action_controller/metal/helpers.rb +64 -67
  40. data/lib/action_controller/metal/http_authentication.rb +212 -199
  41. data/lib/action_controller/metal/implicit_render.rb +21 -17
  42. data/lib/action_controller/metal/instrumentation.rb +22 -12
  43. data/lib/action_controller/metal/live.rb +125 -92
  44. data/lib/action_controller/metal/logging.rb +6 -4
  45. data/lib/action_controller/metal/mime_responds.rb +151 -142
  46. data/lib/action_controller/metal/parameter_encoding.rb +34 -32
  47. data/lib/action_controller/metal/params_wrapper.rb +58 -58
  48. data/lib/action_controller/metal/permissions_policy.rb +14 -13
  49. data/lib/action_controller/metal/rate_limiting.rb +62 -0
  50. data/lib/action_controller/metal/redirecting.rb +110 -84
  51. data/lib/action_controller/metal/renderers.rb +50 -49
  52. data/lib/action_controller/metal/rendering.rb +103 -82
  53. data/lib/action_controller/metal/request_forgery_protection.rb +279 -161
  54. data/lib/action_controller/metal/rescue.rb +12 -8
  55. data/lib/action_controller/metal/streaming.rb +174 -132
  56. data/lib/action_controller/metal/strong_parameters.rb +598 -473
  57. data/lib/action_controller/metal/testing.rb +2 -0
  58. data/lib/action_controller/metal/url_for.rb +23 -14
  59. data/lib/action_controller/metal.rb +145 -61
  60. data/lib/action_controller/railtie.rb +25 -9
  61. data/lib/action_controller/railties/helpers.rb +2 -0
  62. data/lib/action_controller/renderer.rb +105 -66
  63. data/lib/action_controller/template_assertions.rb +4 -2
  64. data/lib/action_controller/test_case.rb +157 -128
  65. data/lib/action_controller.rb +17 -3
  66. data/lib/action_dispatch/constants.rb +34 -0
  67. data/lib/action_dispatch/deprecator.rb +9 -0
  68. data/lib/action_dispatch/http/cache.rb +28 -29
  69. data/lib/action_dispatch/http/content_disposition.rb +2 -0
  70. data/lib/action_dispatch/http/content_security_policy.rb +48 -45
  71. data/lib/action_dispatch/http/filter_parameters.rb +18 -8
  72. data/lib/action_dispatch/http/filter_redirect.rb +22 -1
  73. data/lib/action_dispatch/http/headers.rb +23 -21
  74. data/lib/action_dispatch/http/mime_negotiation.rb +37 -48
  75. data/lib/action_dispatch/http/mime_type.rb +60 -30
  76. data/lib/action_dispatch/http/mime_types.rb +5 -1
  77. data/lib/action_dispatch/http/parameters.rb +12 -10
  78. data/lib/action_dispatch/http/permissions_policy.rb +32 -27
  79. data/lib/action_dispatch/http/rack_cache.rb +4 -0
  80. data/lib/action_dispatch/http/request.rb +132 -79
  81. data/lib/action_dispatch/http/response.rb +136 -103
  82. data/lib/action_dispatch/http/upload.rb +19 -15
  83. data/lib/action_dispatch/http/url.rb +75 -73
  84. data/lib/action_dispatch/journey/formatter.rb +19 -6
  85. data/lib/action_dispatch/journey/gtg/builder.rb +4 -3
  86. data/lib/action_dispatch/journey/gtg/simulator.rb +2 -0
  87. data/lib/action_dispatch/journey/gtg/transition_table.rb +10 -8
  88. data/lib/action_dispatch/journey/nfa/dot.rb +2 -0
  89. data/lib/action_dispatch/journey/nodes/node.rb +6 -5
  90. data/lib/action_dispatch/journey/parser.rb +4 -3
  91. data/lib/action_dispatch/journey/parser_extras.rb +2 -0
  92. data/lib/action_dispatch/journey/path/pattern.rb +18 -15
  93. data/lib/action_dispatch/journey/route.rb +12 -9
  94. data/lib/action_dispatch/journey/router/utils.rb +16 -15
  95. data/lib/action_dispatch/journey/router.rb +13 -10
  96. data/lib/action_dispatch/journey/routes.rb +6 -4
  97. data/lib/action_dispatch/journey/scanner.rb +4 -2
  98. data/lib/action_dispatch/journey/visitors.rb +2 -0
  99. data/lib/action_dispatch/journey.rb +2 -0
  100. data/lib/action_dispatch/log_subscriber.rb +25 -0
  101. data/lib/action_dispatch/middleware/actionable_exceptions.rb +7 -6
  102. data/lib/action_dispatch/middleware/assume_ssl.rb +27 -0
  103. data/lib/action_dispatch/middleware/callbacks.rb +4 -0
  104. data/lib/action_dispatch/middleware/cookies.rb +192 -194
  105. data/lib/action_dispatch/middleware/debug_exceptions.rb +36 -27
  106. data/lib/action_dispatch/middleware/debug_locks.rb +18 -13
  107. data/lib/action_dispatch/middleware/debug_view.rb +9 -2
  108. data/lib/action_dispatch/middleware/exception_wrapper.rb +181 -27
  109. data/lib/action_dispatch/middleware/executor.rb +9 -1
  110. data/lib/action_dispatch/middleware/flash.rb +65 -46
  111. data/lib/action_dispatch/middleware/host_authorization.rb +22 -17
  112. data/lib/action_dispatch/middleware/public_exceptions.rb +12 -8
  113. data/lib/action_dispatch/middleware/reloader.rb +9 -5
  114. data/lib/action_dispatch/middleware/remote_ip.rb +88 -83
  115. data/lib/action_dispatch/middleware/request_id.rb +15 -8
  116. data/lib/action_dispatch/middleware/server_timing.rb +8 -6
  117. data/lib/action_dispatch/middleware/session/abstract_store.rb +7 -0
  118. data/lib/action_dispatch/middleware/session/cache_store.rb +14 -7
  119. data/lib/action_dispatch/middleware/session/cookie_store.rb +32 -25
  120. data/lib/action_dispatch/middleware/session/mem_cache_store.rb +9 -3
  121. data/lib/action_dispatch/middleware/show_exceptions.rb +42 -28
  122. data/lib/action_dispatch/middleware/ssl.rb +60 -45
  123. data/lib/action_dispatch/middleware/stack.rb +15 -9
  124. data/lib/action_dispatch/middleware/static.rb +40 -34
  125. data/lib/action_dispatch/middleware/templates/rescues/_actions.html.erb +2 -2
  126. data/lib/action_dispatch/middleware/templates/rescues/_message_and_suggestions.html.erb +4 -4
  127. data/lib/action_dispatch/middleware/templates/rescues/_source.html.erb +8 -1
  128. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb +7 -7
  129. data/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb +2 -2
  130. data/lib/action_dispatch/middleware/templates/rescues/layout.erb +17 -0
  131. data/lib/action_dispatch/middleware/templates/rescues/missing_exact_template.html.erb +16 -12
  132. data/lib/action_dispatch/middleware/templates/rescues/missing_template.html.erb +1 -1
  133. data/lib/action_dispatch/middleware/templates/rescues/routing_error.html.erb +3 -3
  134. data/lib/action_dispatch/middleware/templates/rescues/template_error.html.erb +4 -4
  135. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.html.erb +1 -1
  136. data/lib/action_dispatch/middleware/templates/rescues/unknown_action.text.erb +1 -1
  137. data/lib/action_dispatch/middleware/templates/routes/_route.html.erb +3 -0
  138. data/lib/action_dispatch/middleware/templates/routes/_table.html.erb +47 -38
  139. data/lib/action_dispatch/railtie.rb +12 -4
  140. data/lib/action_dispatch/request/session.rb +39 -27
  141. data/lib/action_dispatch/request/utils.rb +10 -3
  142. data/lib/action_dispatch/routing/endpoint.rb +2 -0
  143. data/lib/action_dispatch/routing/inspector.rb +59 -9
  144. data/lib/action_dispatch/routing/mapper.rb +686 -639
  145. data/lib/action_dispatch/routing/polymorphic_routes.rb +70 -61
  146. data/lib/action_dispatch/routing/redirection.rb +52 -38
  147. data/lib/action_dispatch/routing/route_set.rb +106 -62
  148. data/lib/action_dispatch/routing/routes_proxy.rb +16 -19
  149. data/lib/action_dispatch/routing/url_for.rb +131 -122
  150. data/lib/action_dispatch/routing.rb +152 -150
  151. data/lib/action_dispatch/system_test_case.rb +91 -81
  152. data/lib/action_dispatch/system_testing/browser.rb +27 -19
  153. data/lib/action_dispatch/system_testing/driver.rb +16 -22
  154. data/lib/action_dispatch/system_testing/server.rb +2 -0
  155. data/lib/action_dispatch/system_testing/test_helpers/screenshot_helper.rb +53 -31
  156. data/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb +2 -0
  157. data/lib/action_dispatch/testing/assertion_response.rb +9 -7
  158. data/lib/action_dispatch/testing/assertions/response.rb +36 -26
  159. data/lib/action_dispatch/testing/assertions/routing.rb +203 -95
  160. data/lib/action_dispatch/testing/assertions.rb +5 -1
  161. data/lib/action_dispatch/testing/integration.rb +240 -229
  162. data/lib/action_dispatch/testing/request_encoder.rb +6 -1
  163. data/lib/action_dispatch/testing/test_helpers/page_dump_helper.rb +35 -0
  164. data/lib/action_dispatch/testing/test_process.rb +14 -9
  165. data/lib/action_dispatch/testing/test_request.rb +4 -2
  166. data/lib/action_dispatch/testing/test_response.rb +34 -19
  167. data/lib/action_dispatch.rb +52 -21
  168. data/lib/action_pack/gem_version.rb +6 -4
  169. data/lib/action_pack/version.rb +3 -1
  170. data/lib/action_pack.rb +18 -17
  171. metadata +86 -27
@@ -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 # :nodoc:
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,123 @@ 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-Control).
80
+ #
81
+ # `:template`
82
+ # : By default, the template digest for the current controller/action is
83
+ # included in ETags. If the action renders a different template, you can
84
+ # include its digest instead. If the action doesn't render a template at
85
+ # all, you can pass `template: false` to skip any attempt to check for a
86
+ # template digest.
87
+ #
88
+ #
89
+ # #### Examples
90
+ #
91
+ # def show
92
+ # @article = Article.find(params[:id])
93
+ # fresh_when(etag: @article, last_modified: @article.updated_at, public: true)
94
+ # end
95
+ #
96
+ # This will send a `304 Not Modified` response if the request specifies a
97
+ # matching ETag and `If-Modified-Since` header. Otherwise, it will render the
98
+ # `show` template.
90
99
  #
91
100
  # You can also just pass a record:
92
101
  #
93
- # def show
94
- # @article = Article.find(params[:id])
95
- # fresh_when(@article)
96
- # end
102
+ # def show
103
+ # @article = Article.find(params[:id])
104
+ # fresh_when(@article)
105
+ # end
97
106
  #
98
- # +etag+ will be set to the record, and +last_modified+ will be set to the
99
- # record's +updated_at+.
107
+ # `etag` will be set to the record, and `last_modified` will be set to the
108
+ # record's `updated_at`.
100
109
  #
101
- # You can also pass an object that responds to +maximum+, such as a
102
- # collection of records:
110
+ # You can also pass an object that responds to `maximum`, such as a collection
111
+ # of records:
103
112
  #
104
- # def index
105
- # @articles = Article.all
106
- # fresh_when(@articles)
107
- # end
113
+ # def index
114
+ # @articles = Article.all
115
+ # fresh_when(@articles)
116
+ # end
108
117
  #
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).
118
+ # In this case, `etag` will be set to the collection, and `last_modified` will
119
+ # be set to `maximum(:updated_at)` (the timestamp of the most recently updated
120
+ # record).
112
121
  #
113
- # When passing a record or a collection, you can still specify other
114
- # options, such as +:public+ and +:cache_control+:
122
+ # When passing a record or a collection, you can still specify other options,
123
+ # such as `:public` and `:cache_control`:
115
124
  #
116
- # def show
117
- # @article = Article.find(params[:id])
118
- # fresh_when(@article, public: true, cache_control: { no_cache: true })
119
- # end
125
+ # def show
126
+ # @article = Article.find(params[:id])
127
+ # fresh_when(@article, public: true, cache_control: { no_cache: true })
128
+ # end
120
129
  #
121
- # The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
130
+ # The above will set `Cache-Control: public, no-cache` in the response.
122
131
  #
123
132
  # When rendering a different template than the controller/action's default
124
133
  # template, you can indicate which digest to include in the ETag:
125
134
  #
126
- # before_action { fresh_when @article, template: "widgets/show" }
135
+ # before_action { fresh_when @article, template: "widgets/show" }
127
136
  #
128
137
  def fresh_when(object = nil, etag: nil, weak_etag: nil, strong_etag: nil, last_modified: nil, public: false, cache_control: {}, template: nil)
129
138
  response.cache_control.delete(:no_store)
@@ -145,131 +154,135 @@ module ActionController
145
154
  head :not_modified if request.fresh?(response)
146
155
  end
147
156
 
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.
157
+ # Sets the `etag` and/or `last_modified` on the response and checks them against
158
+ # the request. If the request doesn't match the provided options, it is
159
+ # considered stale, and the response should be rendered from scratch. Otherwise,
160
+ # it is fresh, and a `304 Not Modified` is sent.
152
161
  #
153
- # ==== Options
162
+ # #### Options
154
163
  #
155
164
  # See #fresh_when for supported options.
156
165
  #
157
- # ==== Examples
166
+ # #### Examples
158
167
  #
159
- # def show
160
- # @article = Article.find(params[:id])
168
+ # def show
169
+ # @article = Article.find(params[:id])
161
170
  #
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
171
+ # if stale?(etag: @article, last_modified: @article.updated_at)
172
+ # @statistics = @article.really_expensive_call
173
+ # respond_to do |format|
174
+ # # all the supported formats
175
+ # end
166
176
  # end
167
177
  # end
168
- # end
169
178
  #
170
179
  # You can also just pass a record:
171
180
  #
172
- # def show
173
- # @article = Article.find(params[:id])
181
+ # def show
182
+ # @article = Article.find(params[:id])
174
183
  #
175
- # if stale?(@article)
176
- # @statistics = @article.really_expensive_call
177
- # respond_to do |format|
178
- # # all the supported formats
184
+ # if stale?(@article)
185
+ # @statistics = @article.really_expensive_call
186
+ # respond_to do |format|
187
+ # # all the supported formats
188
+ # end
179
189
  # end
180
190
  # end
181
- # end
182
191
  #
183
- # +etag+ will be set to the record, and +last_modified+ will be set to the
184
- # record's +updated_at+.
192
+ # `etag` will be set to the record, and `last_modified` will be set to the
193
+ # record's `updated_at`.
185
194
  #
186
- # You can also pass an object that responds to +maximum+, such as a
187
- # collection of records:
195
+ # You can also pass an object that responds to `maximum`, such as a collection
196
+ # of records:
188
197
  #
189
- # def index
190
- # @articles = Article.all
198
+ # def index
199
+ # @articles = Article.all
191
200
  #
192
- # if stale?(@articles)
193
- # @statistics = @articles.really_expensive_call
194
- # respond_to do |format|
195
- # # all the supported formats
201
+ # if stale?(@articles)
202
+ # @statistics = @articles.really_expensive_call
203
+ # respond_to do |format|
204
+ # # all the supported formats
205
+ # end
196
206
  # end
197
207
  # end
198
- # end
199
208
  #
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).
209
+ # In this case, `etag` will be set to the collection, and `last_modified` will
210
+ # be set to `maximum(:updated_at)` (the timestamp of the most recently updated
211
+ # record).
203
212
  #
204
- # When passing a record or a collection, you can still specify other
205
- # options, such as +:public+ and +:cache_control+:
213
+ # When passing a record or a collection, you can still specify other options,
214
+ # such as `:public` and `:cache_control`:
206
215
  #
207
- # def show
208
- # @article = Article.find(params[:id])
216
+ # def show
217
+ # @article = Article.find(params[:id])
209
218
  #
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
219
+ # if stale?(@article, public: true, cache_control: { no_cache: true })
220
+ # @statistics = @articles.really_expensive_call
221
+ # respond_to do |format|
222
+ # # all the supported formats
223
+ # end
214
224
  # end
215
225
  # end
216
- # end
217
226
  #
218
- # The above will set <tt>Cache-Control: public, no-cache</tt> in the response.
227
+ # The above will set `Cache-Control: public, no-cache` in the response.
219
228
  #
220
229
  # When rendering a different template than the controller/action's default
221
230
  # template, you can indicate which digest to include in the ETag:
222
231
  #
223
- # def show
224
- # super if stale?(@article, template: "widgets/show")
225
- # end
232
+ # def show
233
+ # super if stale?(@article, template: "widgets/show")
234
+ # end
226
235
  #
227
236
  def stale?(object = nil, **freshness_kwargs)
228
237
  fresh_when(object, **freshness_kwargs)
229
238
  !request.fresh?(response)
230
239
  end
231
240
 
232
- # Sets the +Cache-Control+ header, overwriting existing directives. This
233
- # method will also ensure an HTTP +Date+ header for client compatibility.
241
+ # Sets the `Cache-Control` header, overwriting existing directives. This method
242
+ # will also ensure an HTTP `Date` header for client compatibility.
243
+ #
244
+ # Defaults to issuing the `private` directive, so that intermediate caches must
245
+ # not cache the response.
246
+ #
247
+ # #### Options
248
+ #
249
+ # `:public`
250
+ # : If true, replaces the default `private` directive with the `public`
251
+ # directive.
252
+ #
253
+ # `:must_revalidate`
254
+ # : If true, adds the `must-revalidate` directive.
234
255
  #
235
- # Defaults to issuing the +private+ directive, so that intermediate caches
236
- # must not cache the response.
256
+ # `:stale_while_revalidate`
257
+ # : Sets the value of the `stale-while-revalidate` directive.
237
258
  #
238
- # ==== Options
259
+ # `:stale_if_error`
260
+ # : Sets the value of the `stale-if-error` directive.
239
261
  #
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
262
  #
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].
263
+ # Any additional key-value pairs are concatenated as directives. For a list of
264
+ # supported `Cache-Control` directives, see the [article on
265
+ # MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control).
253
266
  #
254
- # ==== Examples
267
+ # #### Examples
255
268
  #
256
- # expires_in 10.minutes
257
- # # => Cache-Control: max-age=600, private
269
+ # expires_in 10.minutes
270
+ # # => Cache-Control: max-age=600, private
258
271
  #
259
- # expires_in 10.minutes, public: true
260
- # # => Cache-Control: max-age=600, public
272
+ # expires_in 10.minutes, public: true
273
+ # # => Cache-Control: max-age=600, public
261
274
  #
262
- # expires_in 10.minutes, public: true, must_revalidate: true
263
- # # => Cache-Control: max-age=600, public, must-revalidate
275
+ # expires_in 10.minutes, public: true, must_revalidate: true
276
+ # # => Cache-Control: max-age=600, public, must-revalidate
264
277
  #
265
- # expires_in 1.hour, stale_while_revalidate: 60.seconds
266
- # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
278
+ # expires_in 1.hour, stale_while_revalidate: 60.seconds
279
+ # # => Cache-Control: max-age=3600, private, stale-while-revalidate=60
267
280
  #
268
- # expires_in 1.hour, stale_if_error: 5.minutes
269
- # # => Cache-Control: max-age=3600, private, stale-if-error=300
281
+ # expires_in 1.hour, stale_if_error: 5.minutes
282
+ # # => Cache-Control: max-age=3600, private, stale-if-error=300
270
283
  #
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
284
+ # expires_in 1.hour, public: true, "s-maxage": 3.hours, "no-transform": true
285
+ # # => Cache-Control: max-age=3600, public, s-maxage=10800, no-transform=true
273
286
  #
274
287
  def expires_in(seconds, options = {})
275
288
  response.cache_control.delete(:no_store)
@@ -286,8 +299,8 @@ module ActionController
286
299
  response.date = Time.now unless response.date?
287
300
  end
288
301
 
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.
302
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-cache`. This means the resource
303
+ # will be marked as stale, so clients must always revalidate.
291
304
  # Intermediate/browser caches may still store the asset.
292
305
  def expires_now
293
306
  response.cache_control.replace(no_cache: true)
@@ -295,12 +308,12 @@ module ActionController
295
308
 
296
309
  # Cache or yield the block. The cache is supposed to never expire.
297
310
  #
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.
311
+ # You can use this method when you have an HTTP response that never changes, and
312
+ # the browser and proxies should cache it indefinitely.
300
313
  #
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.
314
+ # * `public`: By default, HTTP responses are private, cached only on the
315
+ # user's web browser. To allow proxies to cache the response, set `true` to
316
+ # indicate that they can serve the cached response to all users.
304
317
  def http_cache_forever(public: false)
305
318
  expires_in 100.years, public: public
306
319
 
@@ -309,8 +322,8 @@ module ActionController
309
322
  public: public)
310
323
  end
311
324
 
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.
325
+ # Sets an HTTP 1.1 `Cache-Control` header of `no-store`. This means the resource
326
+ # may not be stored in any cache.
314
327
  def no_store
315
328
  response.cache_control.replace(no_store: true)
316
329
  end