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.
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