Merge multi_fetch_fragments.

Makes caching a collection of template partials faster using `read_multi`
on the Rails cache store.

Some caching implementations have optimized `read_multi` so we don't have
to check in the cache store for every template.
This commit is contained in:
Kasper Timm Hansen 2015-02-15 20:34:18 +01:00
parent 66a30dc322
commit e56c635427
6 changed files with 95 additions and 2 deletions

View File

@ -161,6 +161,14 @@ module ActionView
end
end
# Given a key (as described in ActionController::Caching::Fragments.expire_fragment),
# returns a key suitable for use in reading, writing, or expiring a
# cached fragment. All keys are prefixed with <tt>views/</tt> and uses
# ActiveSupport::Cache.expand_cache_key for the expansion.
def fragment_cache_key(key)
ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views)
end
private
def fragment_name_with_digest(name) #:nodoc:

View File

@ -36,6 +36,12 @@ module ActionView
end
end
initializer "action_view.collection_caching" do |app|
ActiveSupport.on_load(:action_controller) do
PartialRenderer.collection_cache = app.config.action_controller.cache_store
end
end
initializer "action_view.setup_action_pack" do |app|
ActiveSupport.on_load(:action_controller) do
ActionView::RoutingUrlFor.include(ActionDispatch::Routing::UrlFor)

View File

@ -1,3 +1,4 @@
require 'action_view/renderer/partial_renderer/collection_caching'
require 'thread_safe'
module ActionView
@ -280,6 +281,8 @@ module ActionView
# <%- end -%>
# <% end %>
class PartialRenderer < AbstractRenderer
include CollectionCaching
PREFIXED_PARTIAL_NAMES = ThreadSafe::Cache.new do |h, k|
h[k] = ThreadSafe::Cache.new
end
@ -321,8 +324,9 @@ module ActionView
spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals)
end
result = @template ? collection_with_template : collection_without_template
result.join(spacer).html_safe
cache_collection_render do
@template ? collection_with_template : collection_without_template
end.join(spacer).html_safe
end
def render_partial

View File

@ -0,0 +1,57 @@
require 'active_support/core_ext/object/try'
module ActionView
module CollectionCaching # :nodoc:
extend ActiveSupport::Concern
included do
# Fallback cache store if Action View is used without Rails.
# Otherwise overriden in Railtie to use Rails.cache.
mattr_accessor(:collection_cache) { ActiveSupport::Cache::MemoryStore.new }
end
private
def cache_collection_render
return yield unless cache_collection?
keyed_collection = collection_by_cache_keys
partial_cache = collection_cache.read_multi(*keyed_collection.keys)
@collection = keyed_collection.reject { |key, _| partial_cache.key?(key) }.values
rendered_partials = @collection.any? ? yield.dup : []
fetch_or_cache_partial(partial_cache, order_by: keyed_collection.each_key) do
rendered_partials.shift
end
end
def cache_collection?
@options[:cache].present?
end
def collection_by_cache_keys
seed = @options[:cache].respond_to?(:call) ? @options[:cache] : ->(i) { i }
@collection.each_with_object({}) do |item, hash|
hash[expanded_cache_key(seed.call(item))] = item
end
end
def expanded_cache_key(key)
key = @view.fragment_cache_key(@view.cache_fragment_name(key))
key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
end
def fetch_or_cache_partial(cached_partials, order_by:)
cache_options = @options[:cache_options] || @locals[:cache_options] || {}
order_by.map do |key|
cached_partials.fetch(key) do
yield.tap do |rendered_partial|
collection_cache.write(key, rendered_partial, cache_options)
end
end
end
end
end
end

View File

@ -31,6 +31,10 @@ class Customer < Struct.new(:name, :id)
def persisted?
id.present?
end
def cache_key
name.to_s
end
end
module Quiz

View File

@ -597,3 +597,17 @@ class LazyViewRenderTest < ActiveSupport::TestCase
silence_warnings { Encoding.default_external = old }
end
end
class CachedCollectionViewRenderTest < CachedViewRenderTest
test "with custom key" do
customer = Customer.new("david")
key = ActionController::Base.new.fragment_cache_key([customer, 'key'])
ActionView::PartialRenderer.collection_cache.write(key, 'Hello')
assert_equal "Hello",
@view.render(partial: "test/customer", collection: [customer], cache: ->(item) { [item, 'key'] })
ActionView::PartialRenderer.collection_cache.clear
end
end