From e2e98ef0b8a575712707e4297c5206e2ed7a7d90 Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Thu, 3 Jan 2008 15:35:10 +0000 Subject: Made fragment caching in views work for rjs and builder as well (closes #6642) [zsombor] git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8542 5ecf4fe2-1ee6-0310-87b1-e25e094e27de --- actionpack/CHANGELOG | 2 + actionpack/lib/action_controller/caching.rb | 32 ++++- actionpack/lib/action_view/helpers/cache_helper.rb | 16 ++- actionpack/test/controller/caching_test.rb | 150 +++++++++++++++++++-- 4 files changed, 187 insertions(+), 13 deletions(-) diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index bbac76bc8c..0f7df53f6b 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,7 @@ *SVN* +* Made fragment caching in views work for rjs and builder as well #6642 [zsombor] + * Fixed rendering of partials with layout when done from site layout #9209 [antramm] * Fix atom_feed_helper to comply with the atom spec. Closes #10672 [xaviershay] diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index e3e48f660b..024f633d7c 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -371,11 +371,10 @@ module ActionController #:nodoc: name.is_a?(Hash) ? url_for(name).split("://").last : name end - # Called by CacheHelper#cache - def cache_erb_fragment(block, name = {}, options = nil) + def fragment_for(block, name = {}, options = nil) #:nodoc: unless perform_caching then block.call; return end - buffer = eval(ActionView::Base.erb_variable, block.binding) + buffer = yield if cache = read_fragment(name, options) buffer.concat(cache) @@ -386,6 +385,33 @@ module ActionController #:nodoc: end end + # Called by CacheHelper#cache + def cache_rxml_fragment(block, name = {}, options = nil) #:nodoc: + fragment_for(block, name, options) do + eval('xml.target!', block.binding) + end + end + + # Called by CacheHelper#cache + def cache_rjs_fragment(block, name = {}, options = nil) #:nodoc: + fragment_for(block, name, options) do + begin + debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false + eval('page.to_s', block.binding) + ensure + ActionView::Base.debug_rjs = debug_mode + end + end + end + + # Called by CacheHelper#cache + def cache_erb_fragment(block, name = {}, options = nil) #:nodoc: + fragment_for(block, name, options) do + eval(ActionView::Base.erb_variable, block.binding) + end + end + + # Writes content to the location signified by name (see expire_fragment for acceptable formats) def write_fragment(name, content, options = nil) return unless perform_caching diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index cf5420a35e..87a8e8dc98 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -32,7 +32,21 @@ module ActionView # Topics listed alphabetically # <% end %> def cache(name = {}, &block) - @controller.cache_erb_fragment(block, name) + template_extension = first_render[/\.(\w+)$/, 1].to_sym + case template_extension + when :erb, :rhtml + @controller.cache_erb_fragment(block, name) + when :rjs + @controller.cache_rjs_fragment(block, name) + when :builder, :rxml + @controller.cache_rxml_fragment(block, name) + else + # do a last ditch effort for those brave souls using + # different template engines. This should give plugin + # writters a simple hook. + raise "fragment caching not supported for #{template_extension} files." unless @controller.respond_to?("cache_#{template_extension}_fragment") + @controller.send "cache_#{template_extension}_fragment", block, name + end end end end diff --git a/actionpack/test/controller/caching_test.rb b/actionpack/test/controller/caching_test.rb index d6982fbc86..cfc41cc279 100644 --- a/actionpack/test/controller/caching_test.rb +++ b/actionpack/test/controller/caching_test.rb @@ -25,17 +25,17 @@ class PageCachingTestController < ActionController::Base def not_found head :not_found end - + def custom_path render :text => "Super soaker" cache_page("Super soaker", "/index.html") end - + def expire_custom_path expire_page("/index.html") head :ok end - + def trailing_slash render :text => "Sneak attack" end @@ -95,7 +95,7 @@ class PageCachingTest < Test::Unit::TestCase get :expire_custom_path assert !File.exist?("#{FILE_STORE_PATH}/index.html") end - + def test_should_cache_without_trailing_slash_on_url @controller.class.cache_page 'cached content', '/page_caching_test/trailing_slash' assert File.exist?("#{FILE_STORE_PATH}/page_caching_test/trailing_slash.html") @@ -215,7 +215,7 @@ class ActionCacheTest < Test::Unit::TestCase get :index assert_equal cached_time, @response.body end - + def test_action_cache_with_custom_cache_path get :show cached_time = content_to_cache @@ -262,9 +262,9 @@ class ActionCacheTest < Test::Unit::TestCase @request.host = 'jamis.hostname.com' get :index jamis_cache = content_to_cache - + reset! - + @request.host = 'david.hostname.com' get :index david_cache = content_to_cache @@ -308,7 +308,7 @@ class ActionCacheTest < Test::Unit::TestCase assert_equal 'xml', path_object.extension assert_equal 'example.org/posts/index.xml', path_object.path end - + def test_correct_content_type_is_returned_for_cache_hit # run it twice to cache it the first time get :index, :id => 'content-type.xml' @@ -341,9 +341,141 @@ class ActionCacheTest < Test::Unit::TestCase @controller = ActionCachingTestController.new @request.host = 'hostname.com' end - + def assert_cache_exists(path) full_path = File.join(FILE_STORE_PATH, path + '.cache') assert File.exist?(full_path), "#{full_path.inspect} does not exist." end end + +class FragmentCachingTestController < ActionController::Base + def some_action; end; +end + +class FragmentCachingTest < Test::Unit::TestCase + def setup + ActionController::Base.perform_caching = true + @store = ActionController::Caching::Fragments::UnthreadedMemoryStore.new + ActionController::Base.fragment_cache_store = @store + @controller = FragmentCachingTestController.new + @params = {:controller => 'posts', :action => 'index'} + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + @controller.params = @params + @controller.request = @request + @controller.response = @response + @controller.send(:initialize_current_url) + end + + def test_fragement_cache_key + assert_equal 'what a key', @controller.fragment_cache_key('what a key') + assert_equal( "test.host/fragment_caching_test/some_action", + @controller.fragment_cache_key(:controller => 'fragment_caching_test',:action => 'some_action')) + end + + def test_read_fragment__with_caching_enabled + @store.write('name', 'value') + assert_equal 'value', @controller.read_fragment('name') + end + + def test_read_fragment__with_caching_disabled + ActionController::Base.perform_caching = false + @store.write('name', 'value') + assert_nil @controller.read_fragment('name') + end + + def test_write_fragment__with_caching_enabled + assert_nil @store.read('name') + assert_equal 'value', @controller.write_fragment('name', 'value') + assert_equal 'value', @store.read('name') + end + + def test_write_fragment__with_caching_disabled + assert_nil @store.read('name') + ActionController::Base.perform_caching = false + assert_equal nil, @controller.write_fragment('name', 'value') + assert_nil @store.read('name') + end + + def test_expire_fragment__with_simple_key + @store.write('name', 'value') + @controller.expire_fragment 'name' + assert_nil @store.read('name') + end + + def test_expire_fragment__with__regexp + @store.write('name', 'value') + @store.write('another_name', 'another_value') + @store.write('primalgrasp', 'will not expire ;-)') + + @controller.expire_fragment /name/ + + assert_nil @store.read('name') + assert_nil @store.read('another_name') + assert_equal 'will not expire ;-)', @store.read('primalgrasp') + end + + def test_fragment_for__with_disabled_caching + ActionController::Base.perform_caching = false + + @store.write('expensive', 'fragment content') + fragment_computed = false + + buffer = 'generated till now -> ' + @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer } + + assert fragment_computed + assert_equal 'generated till now -> ', buffer + end + + + def test_fragment_for + @store.write('expensive', 'fragment content') + fragment_computed = false + + buffer = 'generated till now -> ' + @controller.fragment_for(Proc.new { fragment_computed = true }, 'expensive') { buffer} + + assert !fragment_computed + assert_equal 'generated till now -> fragment content', buffer + end + + def test_cache_erb_fragment + @store.write('expensive', 'fragment content') + _erbout = 'generated till now -> ' + + assert_equal( 'generated till now -> fragment content', + @controller.cache_erb_fragment(Proc.new{ }, 'expensive')) + end + + def test_cache_rxml_fragment + @store.write('expensive', 'fragment content') + xml = 'generated till now -> ' + class << xml; def target!; to_s; end; end + + assert_equal( 'generated till now -> fragment content', + @controller.cache_rxml_fragment(Proc.new{ }, 'expensive')) + end + + def test_cache_rjs_fragment + @store.write('expensive', 'fragment content') + page = 'generated till now -> ' + + assert_equal( 'generated till now -> fragment content', + @controller.cache_rjs_fragment(Proc.new{ }, 'expensive')) + end + + def test_cache_rjs_fragment_debug_mode_does_not_interfere + @store.write('expensive', 'fragment content') + page = 'generated till now -> ' + + begin + debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, true + assert_equal( 'generated till now -> fragment content', + @controller.cache_rjs_fragment(Proc.new{ }, 'expensive')) + assert ActionView::Base.debug_rjs + ensure + ActionView::Base.debug_rjs = debug_mode + end + end +end -- cgit v1.2.3