actionview 6.1.3.2 → 7.0.0.alpha2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of actionview might be problematic. Click here for more details.

Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +99 -262
  3. data/MIT-LICENSE +1 -1
  4. data/lib/action_view/base.rb +3 -3
  5. data/lib/action_view/buffers.rb +2 -2
  6. data/lib/action_view/cache_expiry.rb +46 -32
  7. data/lib/action_view/dependency_tracker/erb_tracker.rb +154 -0
  8. data/lib/action_view/dependency_tracker/ripper_tracker.rb +59 -0
  9. data/lib/action_view/dependency_tracker.rb +6 -147
  10. data/lib/action_view/digestor.rb +7 -4
  11. data/lib/action_view/flows.rb +4 -4
  12. data/lib/action_view/gem_version.rb +4 -4
  13. data/lib/action_view/helpers/active_model_helper.rb +1 -1
  14. data/lib/action_view/helpers/asset_tag_helper.rb +85 -30
  15. data/lib/action_view/helpers/asset_url_helper.rb +7 -7
  16. data/lib/action_view/helpers/atom_feed_helper.rb +3 -4
  17. data/lib/action_view/helpers/cache_helper.rb +51 -3
  18. data/lib/action_view/helpers/capture_helper.rb +2 -2
  19. data/lib/action_view/helpers/controller_helper.rb +2 -2
  20. data/lib/action_view/helpers/csp_helper.rb +1 -1
  21. data/lib/action_view/helpers/csrf_helper.rb +1 -1
  22. data/lib/action_view/helpers/date_helper.rb +5 -5
  23. data/lib/action_view/helpers/debug_helper.rb +3 -1
  24. data/lib/action_view/helpers/form_helper.rb +72 -12
  25. data/lib/action_view/helpers/form_options_helper.rb +65 -33
  26. data/lib/action_view/helpers/form_tag_helper.rb +73 -30
  27. data/lib/action_view/helpers/javascript_helper.rb +3 -5
  28. data/lib/action_view/helpers/number_helper.rb +3 -4
  29. data/lib/action_view/helpers/output_safety_helper.rb +2 -2
  30. data/lib/action_view/helpers/rendering_helper.rb +1 -1
  31. data/lib/action_view/helpers/sanitize_helper.rb +2 -2
  32. data/lib/action_view/helpers/tag_helper.rb +17 -4
  33. data/lib/action_view/helpers/tags/base.rb +2 -14
  34. data/lib/action_view/helpers/tags/check_box.rb +1 -1
  35. data/lib/action_view/helpers/tags/collection_select.rb +1 -1
  36. data/lib/action_view/helpers/tags/time_field.rb +10 -1
  37. data/lib/action_view/helpers/tags/weekday_select.rb +27 -0
  38. data/lib/action_view/helpers/tags.rb +3 -2
  39. data/lib/action_view/helpers/text_helper.rb +24 -13
  40. data/lib/action_view/helpers/translation_helper.rb +4 -3
  41. data/lib/action_view/helpers/url_helper.rb +122 -80
  42. data/lib/action_view/helpers.rb +25 -25
  43. data/lib/action_view/lookup_context.rb +33 -52
  44. data/lib/action_view/model_naming.rb +1 -1
  45. data/lib/action_view/path_set.rb +16 -22
  46. data/lib/action_view/railtie.rb +15 -2
  47. data/lib/action_view/render_parser.rb +188 -0
  48. data/lib/action_view/renderer/abstract_renderer.rb +2 -2
  49. data/lib/action_view/renderer/partial_renderer.rb +0 -34
  50. data/lib/action_view/renderer/renderer.rb +4 -4
  51. data/lib/action_view/renderer/streaming_template_renderer.rb +3 -3
  52. data/lib/action_view/renderer/template_renderer.rb +6 -2
  53. data/lib/action_view/rendering.rb +2 -2
  54. data/lib/action_view/ripper_ast_parser.rb +198 -0
  55. data/lib/action_view/routing_url_for.rb +1 -1
  56. data/lib/action_view/template/error.rb +108 -13
  57. data/lib/action_view/template/handlers/erb.rb +6 -0
  58. data/lib/action_view/template/handlers.rb +3 -3
  59. data/lib/action_view/template/html.rb +3 -3
  60. data/lib/action_view/template/inline.rb +3 -3
  61. data/lib/action_view/template/raw_file.rb +3 -3
  62. data/lib/action_view/template/resolver.rb +84 -311
  63. data/lib/action_view/template/text.rb +3 -3
  64. data/lib/action_view/template/types.rb +14 -12
  65. data/lib/action_view/template.rb +10 -1
  66. data/lib/action_view/template_details.rb +66 -0
  67. data/lib/action_view/template_path.rb +64 -0
  68. data/lib/action_view/test_case.rb +6 -2
  69. data/lib/action_view/testing/resolvers.rb +11 -12
  70. data/lib/action_view/unbound_template.rb +33 -7
  71. data/lib/action_view.rb +3 -4
  72. data/lib/assets/compiled/rails-ujs.js +2 -2
  73. metadata +25 -18
@@ -17,15 +17,16 @@ module ActionView
17
17
  if dependencies.nil? || dependencies.empty?
18
18
  cache_key = "#{name}.#{format}"
19
19
  else
20
- cache_key = [ name, format, dependencies ].flatten.compact.join(".")
20
+ dependencies_suffix = dependencies.flatten.tap(&:compact!).join(".")
21
+ cache_key = "#{name}.#{format}.#{dependencies_suffix}"
21
22
  end
22
23
 
23
24
  # this is a correctly done double-checked locking idiom
24
25
  # (Concurrent::Map's lookups have volatile semantics)
25
26
  finder.digest_cache[cache_key] || @@digest_mutex.synchronize do
26
27
  finder.digest_cache.fetch(cache_key) do # re-check under lock
27
- partial = name.include?("/_")
28
- root = tree(name, finder, partial)
28
+ path = TemplatePath.parse(name)
29
+ root = tree(path.to_s, finder, path.partial?)
29
30
  dependencies.each do |injected_dep|
30
31
  root.children << Injected.new(injected_dep, nil, nil)
31
32
  end if dependencies
@@ -43,7 +44,9 @@ module ActionView
43
44
  logical_name = name.gsub(%r|/_|, "/")
44
45
  interpolated = name.include?("#")
45
46
 
46
- if !interpolated && (template = find_template(finder, logical_name, [], partial, []))
47
+ path = TemplatePath.parse(name)
48
+
49
+ if !interpolated && (template = find_template(finder, path.name, [path.prefix], partial, []))
47
50
  if node = seen[template.identifier] # handle cycles in the tree
48
51
  node
49
52
  else
@@ -3,7 +3,7 @@
3
3
  require "active_support/core_ext/string/output_safety"
4
4
 
5
5
  module ActionView
6
- class OutputFlow #:nodoc:
6
+ class OutputFlow # :nodoc:
7
7
  attr_reader :content
8
8
 
9
9
  def initialize
@@ -17,17 +17,17 @@ module ActionView
17
17
 
18
18
  # Called by each renderer object to set the layout contents.
19
19
  def set(key, value)
20
- @content[key] = ActiveSupport::SafeBuffer.new(value)
20
+ @content[key] = ActiveSupport::SafeBuffer.new(value.to_s)
21
21
  end
22
22
 
23
23
  # Called by content_for
24
24
  def append(key, value)
25
- @content[key] << value
25
+ @content[key] << value.to_s
26
26
  end
27
27
  alias_method :append!, :append
28
28
  end
29
29
 
30
- class StreamingFlow < OutputFlow #:nodoc:
30
+ class StreamingFlow < OutputFlow # :nodoc:
31
31
  def initialize(view, fiber)
32
32
  @view = view
33
33
  @parent = nil
@@ -7,10 +7,10 @@ module ActionView
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 6
11
- MINOR = 1
12
- TINY = 3
13
- PRE = "2"
10
+ MAJOR = 7
11
+ MINOR = 0
12
+ TINY = 0
13
+ PRE = "alpha2"
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -5,7 +5,7 @@ require "active_support/core_ext/enumerable"
5
5
 
6
6
  module ActionView
7
7
  # = Active Model Helpers
8
- module Helpers #:nodoc:
8
+ module Helpers # :nodoc:
9
9
  module ActiveModelHelper
10
10
  end
11
11
 
@@ -8,7 +8,7 @@ require "action_view/helpers/tag_helper"
8
8
 
9
9
  module ActionView
10
10
  # = Action View Asset Tag Helpers
11
- module Helpers #:nodoc:
11
+ module Helpers # :nodoc:
12
12
  # This module provides methods for generating HTML that links views to assets such
13
13
  # as images, JavaScripts, stylesheets, and feeds. These methods do not verify
14
14
  # the assets exist before linking to them:
@@ -16,14 +16,15 @@ module ActionView
16
16
  # image_tag("rails.png")
17
17
  # # => <img src="/assets/rails.png" />
18
18
  # stylesheet_link_tag("application")
19
- # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" />
19
+ # # => <link href="/assets/application.css?body=1" rel="stylesheet" />
20
20
  module AssetTagHelper
21
- extend ActiveSupport::Concern
22
-
23
21
  include AssetUrlHelper
24
22
  include TagHelper
25
23
 
24
+ mattr_accessor :image_loading
25
+ mattr_accessor :image_decoding
26
26
  mattr_accessor :preload_links_header
27
+ mattr_accessor :apply_stylesheet_media_default
27
28
 
28
29
  # Returns an HTML script tag for each of the +sources+ provided.
29
30
  #
@@ -93,11 +94,12 @@ module ActionView
93
94
  crossorigin = options.delete("crossorigin")
94
95
  crossorigin = "anonymous" if crossorigin == true
95
96
  integrity = options["integrity"]
97
+ rel = options["type"] == "module" ? "modulepreload" : "preload"
96
98
 
97
99
  sources_tags = sources.uniq.map { |source|
98
100
  href = path_to_javascript(source, path_options)
99
- if preload_links_header && !options["defer"]
100
- preload_link = "<#{href}>; rel=preload; as=script"
101
+ if preload_links_header && !options["defer"] && href.present? && !href.start_with?("data:")
102
+ preload_link = "<#{href}>; rel=#{rel}; as=script"
101
103
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
102
104
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
103
105
  preload_link += "; nopush" if nopush
@@ -120,24 +122,41 @@ module ActionView
120
122
  sources_tags
121
123
  end
122
124
 
123
- # Returns a stylesheet link tag for the sources specified as arguments. If
124
- # you don't specify an extension, <tt>.css</tt> will be appended automatically.
125
+ # Returns a stylesheet link tag for the sources specified as arguments.
126
+ #
127
+ # When passing paths, the <tt>.css</tt> extension is optional.
128
+ # If you don't specify an extension, <tt>.css</tt> will be appended automatically.
129
+ # If you do not want <tt>.css</tt> appended to the path,
130
+ # set <tt>extname: false</tt> in the options.
125
131
  # You can modify the link attributes by passing a hash as the last argument.
126
- # For historical reasons, the 'media' attribute will always be present and defaults
127
- # to "screen", so you must explicitly set it to "all" for the stylesheet(s) to
128
- # apply to all media types.
129
132
  #
130
133
  # If the server supports Early Hints header links for these assets will be
131
134
  # automatically pushed.
132
135
  #
136
+ # ==== Options
137
+ #
138
+ # * <tt>:extname</tt> - Append an extension to the generated URL unless the extension
139
+ # already exists. This only applies for relative URLs.
140
+ # * <tt>:protocol</tt> - Sets the protocol of the generated URL. This option only
141
+ # applies when a relative URL and +host+ options are provided.
142
+ # * <tt>:host</tt> - When a relative URL is provided the host is added to the
143
+ # that path.
144
+ # * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
145
+ # when it is set to true.
146
+ #
147
+ # ==== Examples
148
+ #
133
149
  # stylesheet_link_tag "style"
134
- # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
150
+ # # => <link href="/assets/style.css" rel="stylesheet" />
135
151
  #
136
152
  # stylesheet_link_tag "style.css"
137
- # # => <link href="/assets/style.css" media="screen" rel="stylesheet" />
153
+ # # => <link href="/assets/style.css" rel="stylesheet" />
138
154
  #
139
155
  # stylesheet_link_tag "http://www.example.com/style.css"
140
- # # => <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" />
156
+ # # => <link href="http://www.example.com/style.css" rel="stylesheet" />
157
+ #
158
+ # stylesheet_link_tag "style.less", extname: false, skip_pipeline: true, rel: "stylesheet/less"
159
+ # # => <link href="/stylesheets/style.less" rel="stylesheet/less">
141
160
  #
142
161
  # stylesheet_link_tag "style", media: "all"
143
162
  # # => <link href="/assets/style.css" media="all" rel="stylesheet" />
@@ -146,11 +165,11 @@ module ActionView
146
165
  # # => <link href="/assets/style.css" media="print" rel="stylesheet" />
147
166
  #
148
167
  # stylesheet_link_tag "random.styles", "/css/stylish"
149
- # # => <link href="/assets/random.styles" media="screen" rel="stylesheet" />
150
- # # <link href="/css/stylish.css" media="screen" rel="stylesheet" />
168
+ # # => <link href="/assets/random.styles" rel="stylesheet" />
169
+ # # <link href="/css/stylish.css" rel="stylesheet" />
151
170
  def stylesheet_link_tag(*sources)
152
171
  options = sources.extract_options!.stringify_keys
153
- path_options = options.extract!("protocol", "host", "skip_pipeline").symbolize_keys
172
+ path_options = options.extract!("protocol", "extname", "host", "skip_pipeline").symbolize_keys
154
173
  preload_links = []
155
174
  crossorigin = options.delete("crossorigin")
156
175
  crossorigin = "anonymous" if crossorigin == true
@@ -159,7 +178,7 @@ module ActionView
159
178
 
160
179
  sources_tags = sources.uniq.map { |source|
161
180
  href = path_to_stylesheet(source, path_options)
162
- if preload_links_header
181
+ if preload_links_header && href.present? && !href.start_with?("data:")
163
182
  preload_link = "<#{href}>; rel=preload; as=style"
164
183
  preload_link += "; crossorigin=#{crossorigin}" unless crossorigin.nil?
165
184
  preload_link += "; integrity=#{integrity}" unless integrity.nil?
@@ -168,10 +187,14 @@ module ActionView
168
187
  end
169
188
  tag_options = {
170
189
  "rel" => "stylesheet",
171
- "media" => "screen",
172
190
  "crossorigin" => crossorigin,
173
191
  "href" => href
174
192
  }.merge!(options)
193
+
194
+ if apply_stylesheet_media_default && tag_options["media"].blank?
195
+ tag_options["media"] = "screen"
196
+ end
197
+
175
198
  tag(:link, tag_options)
176
199
  }.join("\n").html_safe
177
200
 
@@ -294,7 +317,7 @@ module ActionView
294
317
  # # => <link rel="preload" href="/media/audio.ogg" as="audio" type="audio/ogg" />
295
318
  #
296
319
  def preload_link_tag(source, options = {})
297
- href = asset_path(source, skip_pipeline: options.delete(:skip_pipeline))
320
+ href = path_to_asset(source, skip_pipeline: options.delete(:skip_pipeline))
298
321
  extname = File.extname(source).downcase.delete(".")
299
322
  mime_type = options.delete(:type) || Template::Types[extname]&.to_s
300
323
  as_type = options.delete(:as) || resolve_link_as(extname, mime_type)
@@ -382,6 +405,10 @@ module ActionView
382
405
  end
383
406
 
384
407
  options[:width], options[:height] = extract_dimensions(options.delete(:size)) if options[:size]
408
+
409
+ options[:loading] ||= image_loading if image_loading
410
+ options[:decoding] ||= image_decoding if image_decoding
411
+
385
412
  tag("img", options)
386
413
  end
387
414
 
@@ -503,24 +530,52 @@ module ActionView
503
530
  end
504
531
 
505
532
  def resolve_link_as(extname, mime_type)
506
- if extname == "js"
507
- "script"
508
- elsif extname == "css"
509
- "style"
510
- elsif extname == "vtt"
511
- "track"
512
- elsif (type = mime_type.to_s.split("/")[0]) && type.in?(%w(audio video font))
513
- type
533
+ case extname
534
+ when "js" then "script"
535
+ when "css" then "style"
536
+ when "vtt" then "track"
537
+ else
538
+ mime_type.to_s.split("/").first.presence_in(%w(audio video font image))
514
539
  end
515
540
  end
516
541
 
517
- def send_preload_links_header(preload_links)
542
+ MAX_HEADER_SIZE = 8_000 # Some HTTP client and proxies have a 8kiB header limit
543
+ def send_preload_links_header(preload_links, max_header_size: MAX_HEADER_SIZE)
544
+ return if preload_links.empty?
545
+ return if response.sending?
546
+
518
547
  if respond_to?(:request) && request
519
548
  request.send_early_hints("Link" => preload_links.join("\n"))
520
549
  end
521
550
 
522
551
  if respond_to?(:response) && response
523
- response.headers["Link"] = [response.headers["Link"].presence, *preload_links].compact.join(",")
552
+ header = response.headers["Link"]
553
+ header = header ? header.dup : +""
554
+
555
+ # rindex count characters not bytes, but we assume non-ascii characters
556
+ # are rare in urls, and we have a 192 bytes margin.
557
+ last_line_offset = header.rindex("\n")
558
+ last_line_size = if last_line_offset
559
+ header.bytesize - last_line_offset
560
+ else
561
+ header.bytesize
562
+ end
563
+
564
+ preload_links.each do |link|
565
+ if link.bytesize + last_line_size + 1 < max_header_size
566
+ unless header.empty?
567
+ header << ","
568
+ last_line_size += 1
569
+ end
570
+ else
571
+ header << "\n"
572
+ last_line_size = 0
573
+ end
574
+ header << link
575
+ last_line_size += link.bytesize
576
+ end
577
+
578
+ response.headers["Link"] = header
524
579
  end
525
580
  end
526
581
  end
@@ -4,7 +4,7 @@ require "zlib"
4
4
 
5
5
  module ActionView
6
6
  # = Action View Asset URL Helpers
7
- module Helpers #:nodoc:
7
+ module Helpers # :nodoc:
8
8
  # This module provides methods for generating asset paths and
9
9
  # URLs.
10
10
  #
@@ -31,7 +31,7 @@ module ActionView
31
31
  # image_tag("rails.png")
32
32
  # # => <img src="http://assets.example.com/assets/rails.png" />
33
33
  # stylesheet_link_tag("application")
34
- # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" />
34
+ # # => <link href="http://assets.example.com/assets/application.css" rel="stylesheet" />
35
35
  #
36
36
  # Browsers open a limited number of simultaneous connections to a single
37
37
  # host. The exact number varies by browser and version. This limit may cause
@@ -44,7 +44,7 @@ module ActionView
44
44
  # image_tag("rails.png")
45
45
  # # => <img src="http://assets0.example.com/assets/rails.png" />
46
46
  # stylesheet_link_tag("application")
47
- # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
47
+ # # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
48
48
  #
49
49
  # This may improve the asset loading performance of your application.
50
50
  # It is also possible the combination of additional connection overhead
@@ -65,12 +65,12 @@ module ActionView
65
65
  # +asset_host+ to a proc like this:
66
66
  #
67
67
  # ActionController::Base.asset_host = Proc.new { |source|
68
- # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
68
+ # "http://assets#{OpenSSL::Digest::SHA256.hexdigest(source).to_i(16) % 2 + 1}.example.com"
69
69
  # }
70
70
  # image_tag("rails.png")
71
71
  # # => <img src="http://assets1.example.com/assets/rails.png" />
72
72
  # stylesheet_link_tag("application")
73
- # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" />
73
+ # # => <link href="http://assets2.example.com/assets/application.css" rel="stylesheet" />
74
74
  #
75
75
  # The example above generates "http://assets1.example.com" and
76
76
  # "http://assets2.example.com". This option is useful for example if
@@ -89,7 +89,7 @@ module ActionView
89
89
  # image_tag("rails.png")
90
90
  # # => <img src="http://assets.example.com/assets/rails.png" />
91
91
  # stylesheet_link_tag("application")
92
- # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" />
92
+ # # => <link href="http://stylesheets.example.com/assets/application.css" rel="stylesheet" />
93
93
  #
94
94
  # Alternatively you may ask for a second parameter +request+. That one is
95
95
  # particularly useful for serving assets from an SSL-protected page. The
@@ -190,7 +190,7 @@ module ActionView
190
190
  return "" if source.blank?
191
191
  return source if URI_REGEXP.match?(source)
192
192
 
193
- tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "")
193
+ tail, source = source[/([?#].+)$/], source.sub(/([?#].+)$/, "")
194
194
 
195
195
  if extname = compute_asset_extname(source, options)
196
196
  source = "#{source}#{extname}"
@@ -1,11 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "set"
4
- require "active_support/core_ext/symbol/starts_ends_with"
5
4
 
6
5
  module ActionView
7
6
  # = Action View Atom Feed Helpers
8
- module Helpers #:nodoc:
7
+ module Helpers # :nodoc:
9
8
  module AtomFeedHelper
10
9
  # Adds easy defaults to writing Atom feeds with the Builder template engine (this does not work on ERB or any other
11
10
  # template languages).
@@ -127,7 +126,7 @@ module ActionView
127
126
  end
128
127
  end
129
128
 
130
- class AtomBuilder #:nodoc:
129
+ class AtomBuilder # :nodoc:
131
130
  XHTML_TAG_NAMES = %w(content rights title subtitle summary).to_set
132
131
 
133
132
  def initialize(xml)
@@ -161,7 +160,7 @@ module ActionView
161
160
  end
162
161
  end
163
162
 
164
- class AtomFeedBuilder < AtomBuilder #:nodoc:
163
+ class AtomFeedBuilder < AtomBuilder # :nodoc:
165
164
  def initialize(xml, view, feed_options = {})
166
165
  @xml, @view, @feed_options = xml, view, feed_options
167
166
  end
@@ -2,8 +2,10 @@
2
2
 
3
3
  module ActionView
4
4
  # = Action View Cache Helper
5
- module Helpers #:nodoc:
5
+ module Helpers # :nodoc:
6
6
  module CacheHelper
7
+ class UncacheableFragmentError < StandardError; end
8
+
7
9
  # This helper exposes a method for caching fragments of a view
8
10
  # rather than an entire action or page. This technique is useful
9
11
  # caching pieces like menus, lists of new topics, static HTML
@@ -165,8 +167,10 @@ module ActionView
165
167
  # expire the cache.
166
168
  def cache(name = {}, options = {}, &block)
167
169
  if controller.respond_to?(:perform_caching) && controller.perform_caching
168
- name_options = options.slice(:skip_digest)
169
- safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
170
+ CachingRegistry.track_caching do
171
+ name_options = options.slice(:skip_digest)
172
+ safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
173
+ end
170
174
  else
171
175
  yield
172
176
  end
@@ -174,6 +178,34 @@ module ActionView
174
178
  nil
175
179
  end
176
180
 
181
+ # Returns whether the current view fragment is within a +cache+ block.
182
+ #
183
+ # Useful when certain fragments aren't cacheable:
184
+ #
185
+ # <% cache project do %>
186
+ # <% raise StandardError, "Caching private data!" if caching? %>
187
+ # <% end %>
188
+ def caching?
189
+ CachingRegistry.caching?
190
+ end
191
+
192
+ # Raises +UncacheableFragmentError+ when called from within a +cache+ block.
193
+ #
194
+ # Useful to denote helper methods that can't participate in fragment caching:
195
+ #
196
+ # def project_name_with_time(project)
197
+ # uncacheable!
198
+ # "#{project.name} - #{Time.now}"
199
+ # end
200
+ #
201
+ # # Which will then raise if used within a +cache+ block:
202
+ # <% cache project do %>
203
+ # <%= project_name_with_time(project) %>
204
+ # <% end %>
205
+ def uncacheable!
206
+ raise UncacheableFragmentError, "can't be fragment cached" if caching?
207
+ end
208
+
177
209
  # Cache fragments of a view if +condition+ is true
178
210
  #
179
211
  # <% cache_if admin?, project do %>
@@ -259,6 +291,22 @@ module ActionView
259
291
  end
260
292
  controller.write_fragment(name, fragment, options)
261
293
  end
294
+
295
+ class CachingRegistry
296
+ extend ActiveSupport::PerThreadRegistry
297
+
298
+ attr_accessor :caching
299
+ alias caching? caching
300
+
301
+ def self.track_caching
302
+ caching_was = self.caching
303
+ self.caching = true
304
+
305
+ yield
306
+ ensure
307
+ self.caching = caching_was
308
+ end
309
+ end
262
310
  end
263
311
  end
264
312
  end