diff --git a/lib/multi_fetch_fragments.rb b/lib/multi_fetch_fragments.rb index 42bfd92..ecad080 100644 --- a/lib/multi_fetch_fragments.rb +++ b/lib/multi_fetch_fragments.rb @@ -7,7 +7,6 @@ module MultiFetchFragments private def render_collection_with_multi_fetch_cache - return nil if @collection.blank? if @options.key?(:spacer_template) @@ -17,57 +16,70 @@ def render_collection_with_multi_fetch_cache results = [] if cache_collection? + cache_options = @options.fetch(:cache_options, {}) - additional_cache_options = @options.fetch(:cache_options, {}) - keys_to_collection_map = {} - - @collection.each do |item| + keys_to_item_info_map = {} + @collection.each_with_index do |item, index| key = @options[:cache].respond_to?(:call) ? @options[:cache].call(item) : item - key_with_optional_digest = nil - if defined?(@view.fragment_name_with_digest) - key_with_optional_digest = @view.fragment_name_with_digest(key) - else - key_with_optional_digest = key - end + key = @view.fragment_name_with_digest(key) if @view.respond_to?(:fragment_name_with_digest) - expanded_key = @view.controller.fragment_cache_key(key_with_optional_digest) + expanded_key = @view.controller.fragment_cache_key(key) - keys_to_collection_map[expanded_key] = item + keys_to_item_info_map[expanded_key] = item, index end - # cache.read_multi & cache.write interfaces may require mutable keys, ie. dalli 2.6.0 - mutable_keys = keys_to_collection_map.keys.collect { |key| key.dup } + # cache.read_multi and cache.write interfaces may require mutable keys, ie. dalli 2.6.0 + keys = keys_to_item_info_map.keys.map(&:dup) + + cached_results = @view.controller.instrument_fragment_cache(:read_fragment, keys.join("; ")) do + cached_results = @view.controller.cache_store.read_multi(*keys, cache_options) + + cached_results.each do |key, result| + cached_results[key] = result.html_safe if result.respond_to?(:html_safe) + end - result_hash = Rails.cache.read_multi(mutable_keys) + cached_results + end # if we had a cached value, we don't need to render that object from the collection. # if it wasn't cached, we need to render those objects as before - @collection = (keys_to_collection_map.keys - result_hash.keys).map do |key| - keys_to_collection_map[key] - end + uncached_keys = keys - cached_results.keys + + # @collection_data is only used if no @path could be found that covers all items + use_collection_data = @path.nil? && @collection_data - non_cached_results = [] + uncached_collection = [] + uncached_collection_data = [] if use_collection_data + uncached_keys.each do |key| + item, index = keys_to_item_info_map[key] - # sequentially render any non-cached objects remaining - if @collection.any? - non_cached_results = @template ? collection_with_template : collection_without_template + uncached_collection << item + uncached_collection_data << @collection_data[index] if use_collection_data end - # sort the result according to the keys that were fed in, cache the non-cached results - mutable_keys.each do |key| + @collection = uncached_collection + @collection_data = uncached_collection_data if use_collection_data - cached_value = result_hash[key] - if cached_value - results << cached_value - else - non_cached_result = non_cached_results.shift - Rails.cache.write(key, non_cached_result, additional_cache_options) + # sequentially render any uncached objects remaining + uncached_results = [] + unless @collection.empty? + uncached_results = @template ? collection_with_template : collection_without_template + end + + # sort the result according to the keys that were fed in, cache the uncached results + keys.each do |key| + result = cached_results[key] + if result.nil? + result = uncached_results.shift - results << non_cached_result + @view.controller.instrument_fragment_cache(:write_fragment, key) do + @view.controller.cache_store.write(key, result.try(:to_str), cache_options) + end end + + results << result end - else results = @template ? collection_with_template : collection_without_template end @@ -77,13 +89,13 @@ def render_collection_with_multi_fetch_cache def cache_collection? cache_option = @options[:cache].presence || @locals[:cache].presence - ActionController::Base.perform_caching && cache_option + @view.controller && @view.controller.perform_caching && @view.controller.cache_store && cache_option end class Railtie < Rails::Railtie initializer "multi_fetch_fragments.initialize" do |app| - ActionView::PartialRenderer.class_eval do - include MultiFetchFragments + ActiveSupport.on_load(:action_view) do + ActionView::PartialRenderer.send(:include, MultiFetchFragments) end end end diff --git a/spec/multi_fetch_fragments_spec.rb b/spec/multi_fetch_fragments_spec.rb index db11379..b9fb8a2 100644 --- a/spec/multi_fetch_fragments_spec.rb +++ b/spec/multi_fetch_fragments_spec.rb @@ -4,7 +4,9 @@ it "doesn't smoke" do MultiFetchFragments::Railtie.run_initializers - view = ActionView::Base.new([File.dirname(__FILE__)], {}) + controller = ActionController::Base.new + view = ActionView::Base.new([File.dirname(__FILE__)], {}, controller) + view.render(:partial => "views/customer", :collection => [ Customer.new("david"), Customer.new("mary") ]).should == "Hello: david\nHello: mary\n" end @@ -14,12 +16,13 @@ MultiFetchFragments::Railtie.run_initializers controller = ActionController::Base.new + controller.cache_store = cache_mock view = ActionView::Base.new([File.dirname(__FILE__)], {}, controller) customer = Customer.new("david") key = controller.fragment_cache_key([customer, 'key']) - cache_mock.should_receive(:read_multi).with([key]).and_return({key => 'Hello'}) + cache_mock.should_receive(:read_multi).with(key, {}).and_return({key => 'Hello'}) view.render(:partial => "views/customer", :collection => [ customer ], :cache => Proc.new{ |item| [item, 'key']}).should == "Hello" end