Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 48 additions & 36 deletions lib/multi_fetch_fragments.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ module MultiFetchFragments

private
def render_collection_with_multi_fetch_cache

return nil if @collection.blank?

if @options.key?(:spacer_template)
Expand All @@ -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
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions spec/multi_fetch_fragments_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down