diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index 1a491f1b35..8c6a4a9efa 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,7 @@ +* Add `caching?` helper that returns whether the current code path is being cached and `unacheable!` to denote helper methods that can't participate in fragment caching. + + *Ben Toews*, *John Hawthorn*, *Kasper Timm Hansen*, *Joel Hawksley* + * Add `include_seconds` option for `time_field` <%= form.time_field :foo, include_seconds: false %> diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 3487919006..d149386d14 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -4,6 +4,8 @@ module ActionView # = Action View Cache Helper module Helpers # :nodoc: module CacheHelper + class UncacheableFragmentError < StandardError; end + # This helper exposes a method for caching fragments of a view # rather than an entire action or page. This technique is useful # caching pieces like menus, lists of new topics, static HTML @@ -165,8 +167,10 @@ module ActionView # expire the cache. def cache(name = {}, options = {}, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching - name_options = options.slice(:skip_digest) - safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block)) + CachingRegistry.track_caching do + name_options = options.slice(:skip_digest) + safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block)) + end else yield end @@ -174,6 +178,34 @@ module ActionView nil end + # Returns whether the current view fragment is within a +cache+ block. + # + # Useful when certain fragments aren't cacheable: + # + # <% cache project do %> + # <% raise StandardError, "Caching private data!" if caching? %> + # <% end %> + def caching? + CachingRegistry.caching? + end + + # Raises +UncacheableFragmentError+ when called from within a +cache+ block. + # + # Useful to denote helper methods that can't participate in fragment caching: + # + # def project_name_with_time(project) + # uncacheable! + # "#{project.name} - #{Time.now}" + # end + # + # # Which will then raise if used within a +cache+ block: + # <% cache project do %> + # <%= project_name_with_time(project) %> + # <% end %> + def uncacheable! + raise UncacheableFragmentError, "can't be fragment cached" if caching? + end + # Cache fragments of a view if +condition+ is true # # <% cache_if admin?, project do %> @@ -259,6 +291,22 @@ module ActionView end controller.write_fragment(name, fragment, options) end + + class CachingRegistry + extend ActiveSupport::PerThreadRegistry + + attr_accessor :caching + alias caching? caching + + def self.track_caching + caching_was = self.caching + self.caching = true + + yield + ensure + self.caching = caching_was + end + end end end end diff --git a/actionview/test/fixtures/test/caching_predicate.html.erb b/actionview/test/fixtures/test/caching_predicate.html.erb new file mode 100644 index 0000000000..8b32930067 --- /dev/null +++ b/actionview/test/fixtures/test/caching_predicate.html.erb @@ -0,0 +1,3 @@ +<%= cache "foo" do %> + <%= "Cached!" if caching? %> +<% end %> diff --git a/actionview/test/fixtures/test/caching_predicate_outside_cache.html.erb b/actionview/test/fixtures/test/caching_predicate_outside_cache.html.erb new file mode 100644 index 0000000000..6d7e2d6c9c --- /dev/null +++ b/actionview/test/fixtures/test/caching_predicate_outside_cache.html.erb @@ -0,0 +1 @@ +<%= "Not cached!" unless caching? %> diff --git a/actionview/test/fixtures/test/uncacheable.html.erb b/actionview/test/fixtures/test/uncacheable.html.erb new file mode 100644 index 0000000000..227c83d0c0 --- /dev/null +++ b/actionview/test/fixtures/test/uncacheable.html.erb @@ -0,0 +1,3 @@ +<%= cache "uhoh" do %> + <%= uncacheable! %> +<% end %> diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb index 199d8dd6f4..7ca3025ce3 100644 --- a/actionview/test/template/render_test.rb +++ b/actionview/test/template/render_test.rb @@ -721,6 +721,24 @@ class CachedViewRenderTest < ActiveSupport::TestCase assert_not_equal cat, dog end + + def test_caching_predicate_method + result = @view.render(template: "test/caching_predicate") + + assert_match "Cached!", result + end + + def test_caching_predicate_method_outside_of_cache + result = @view.render(template: "test/caching_predicate_outside_cache") + + assert_match "Not cached!", result + end + + def test_uncacheable + e = assert_raises(ActionView::Template::Error) { @view.render(template: "test/uncacheable") } + + assert_match "can't be fragment cached", e.cause.message + end end class LazyViewRenderTest < ActiveSupport::TestCase