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:
parent
66a30dc322
commit
e56c635427
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue