Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save vojtad/dc7c911ac57c9798fbe8aab867458823 to your computer and use it in GitHub Desktop.
Save vojtad/dc7c911ac57c9798fbe8aab867458823 to your computer and use it in GitHub Desktop.
Monkey patch ActionView::CollectionCaching#cache_collection_render included in ActionView::PartialRenderer. This helps prevent N+1 for uncached entries when rendering cached collection.
module MonkeyPatches
#
# Monkey patch ActionView::CollectionCaching#cache_collection_render included in ActionView::PartialRenderer.
# This helps prevent N+1 for uncached entries when rendering cached collection.
# This patch allows us to specify modify_uncached_collection Proc. The Proc is called with collection of uncached entries as argument. Its result should be Array. It is used as new collection for rendering.
#
# Setup:
#
# Add this line to monkey_patches.rb initializer.
# ActionView::PartialRenderer.prepend(MonkeyPatches::ActionViewPartialRendererCollectionCaching)
#
# Basic usage:
#
# events = Event.select(:id, :updated_at)
#
# eager_load_uncached_proc = Proc.new do |uncached_collection|
# ids = uncached_collection.map(&:id)
# collection = Event.preload(:created_by, jobs: [:person]).where(id: ids)
# collection.order_values = events.order_values # needed to preserve the original order
# collection.to_a
# end
#
# cache_key_proc = Proc.new do |event|
# [:event_list_item, event]
# end
#
# render partial: 'event_list_item', as: :event, collection: events, modify_uncached_collection: eager_load_uncached_proc, cached: cache_key_proc
#
module ActionViewPartialRendererCollectionCaching
private
def cache_collection_render(instrumentation_payload)
return yield unless @options[:cached]
keyed_collection = collection_by_cache_keys
cached_partials = collection_cache.read_multi(*keyed_collection.keys)
instrumentation_payload[:cache_hits] = cached_partials.size
@collection = keyed_collection.reject {|key, _| cached_partials.key?(key)}.values
modify_uncached_collection # this is the added code
rendered_partials = @collection.length == 0 ? [] : yield
index = 0
fetch_or_cache_partial(cached_partials, order_by: keyed_collection.each_key) do
rendered_partials[index].tap {index += 1}
end
end
def modify_uncached_collection
return if @collection.empty?
return unless @options[:modify_uncached_collection]
return unless @options[:modify_uncached_collection].respond_to?(:call)
@collection = @options[:modify_uncached_collection].call(@collection)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment