From 43762e36c46403ac7bfdaf53f9fa789fb5e1f881 Mon Sep 17 00:00:00 2001 From: Scott Walkinshaw Date: Thu, 24 Jan 2013 16:57:56 -0500 Subject: [PATCH 1/5] Bump gem version in Gemfile.lock --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d884453..6e7c0af 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - multi_fetch_fragments (0.0.12) + multi_fetch_fragments (0.0.13) GEM remote: http://rubygems.org/ From cdadfae55dfc59fad585540e033f0f208434f9c2 Mon Sep 17 00:00:00 2001 From: Scott Walkinshaw Date: Thu, 24 Jan 2013 16:58:20 -0500 Subject: [PATCH 2/5] Support short-hand rendering of partials Before: required syntax for rendering was: `render partial: 'partial_name', collection: @collection, cache: true` After: `render @collection, cache: true` works as well. `cache: false` also disables caching as well now. --- lib/multi_fetch_fragments.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/multi_fetch_fragments.rb b/lib/multi_fetch_fragments.rb index 042161a..9cc514d 100644 --- a/lib/multi_fetch_fragments.rb +++ b/lib/multi_fetch_fragments.rb @@ -16,7 +16,7 @@ def render_collection_with_multi_fetch_cache results = [] - if ActionController::Base.perform_caching && @options[:cache].present? + if cache_collection? additional_cache_options = @options.fetch(:cache_options, {}) keys_to_collection_map = {} @@ -81,6 +81,11 @@ def render_collection_with_multi_fetch_cache results.join(spacer).html_safe end + def cache_collection? + cache_option = @options[:cache].presence || @locals[:cache].presence + ActionController::Base.perform_caching && cache_option + end + class Railtie < Rails::Railtie initializer "multi_fetch_fragments.initialize" do |app| ActionView::PartialRenderer.class_eval do From a152eae9c56745fa4982ea78e36af2897e78d7ad Mon Sep 17 00:00:00 2001 From: Scott Walkinshaw Date: Thu, 24 Jan 2013 17:00:42 -0500 Subject: [PATCH 3/5] Use respond_to?(:call) instead of is_a?(Proc) --- lib/multi_fetch_fragments.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/multi_fetch_fragments.rb b/lib/multi_fetch_fragments.rb index 9cc514d..30ae34a 100644 --- a/lib/multi_fetch_fragments.rb +++ b/lib/multi_fetch_fragments.rb @@ -25,8 +25,8 @@ def render_collection_with_multi_fetch_cache @collection = @collection.clone @collection.each do |item| - key = @options[:cache].is_a?(Proc) ? @options[:cache].call(item) : item - + 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) From e244bb530072d32cb9ed934eea30ae50cdf8d900 Mon Sep 17 00:00:00 2001 From: Scott Walkinshaw Date: Thu, 24 Jan 2013 17:01:07 -0500 Subject: [PATCH 4/5] Remove trailing whitespace --- lib/multi_fetch_fragments.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/multi_fetch_fragments.rb b/lib/multi_fetch_fragments.rb index 30ae34a..a209f25 100644 --- a/lib/multi_fetch_fragments.rb +++ b/lib/multi_fetch_fragments.rb @@ -34,7 +34,7 @@ def render_collection_with_multi_fetch_cache key_with_optional_digest = key end - expanded_key = @view.controller.fragment_cache_key(key_with_optional_digest) + expanded_key = @view.controller.fragment_cache_key(key_with_optional_digest) keys_to_collection_map[expanded_key] = item end @@ -44,7 +44,7 @@ def render_collection_with_multi_fetch_cache result_hash = Rails.cache.read_multi(mutable_keys) - # if we had a cached value, we don't need to render that object from the collection. + # 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 result_hash.each do |key, value| if value From 4992feb369c5930e6d8be7fcee5fd9847aa37adf Mon Sep 17 00:00:00 2001 From: Scott Walkinshaw Date: Thu, 24 Jan 2013 17:06:00 -0500 Subject: [PATCH 5/5] Update README with new supported syntax --- README.md | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c13765b..f248cd8 100644 --- a/README.md +++ b/README.md @@ -14,25 +14,33 @@ According to New Relic the test action went from an average of 152 ms to 34 ms. The ideal user of this gem is someone who's rendering and caching a large collection of the same partial. (e.g. Todo lists, rows in a table) -
+
## Syntax -Using this gem, if you want to automatically render a collection and cache each partial with its default cache key: +Using this gem, if you want to automatically render a collection and cache each partial with its default cache key: ```erb <%= render partial: 'item', collection: @items, cache: true %> ``` +Short-hand rendering of partials is also supported: -If you want a custom cache key for this same behavior, use a Proc: +```erb +<%= render @items, cache: true %> +``` + +If you want a custom cache key for this same behavior, use a Proc or lambda (or any object that responds to call): ```erb <%= render partial: 'item', collection: @items, cache: Proc.new{|item| [item, 'show']} %> ``` +Note: `cache: false` also disables the cached rendering. + + ## Background -One of the applications I worked on at the Obama campaign was Dashboard, a virtual field office we created. Dashboard doesn't talk directly to a database. It only speaks to a rest API called Narwhal. You can imagine the performance obstacles we faced building an application this way. So we had to take insane advantage of caching everything we could. This included looking for as many places as possible where we could fetch from Memcached in parallel using Rails' read_multi: +One of the applications I worked on at the Obama campaign was Dashboard, a virtual field office we created. Dashboard doesn't talk directly to a database. It only speaks to a rest API called Narwhal. You can imagine the performance obstacles we faced building an application this way. So we had to take insane advantage of caching everything we could. This included looking for as many places as possible where we could fetch from Memcached in parallel using Rails' read_multi: > read_multi(*names) public @@ -42,15 +50,15 @@ One of the applications I worked on at the Obama campaign was Dashboard, a virtu > Returns a hash mapping the names provided to the values found. -The result of all this is I'm constantly on the lookout for more places where caching can be optimized. And one area I've noticed recently is how us Rails developers render and cache collections of partials. +The result of all this is I'm constantly on the lookout for more places where caching can be optimized. And one area I've noticed recently is how us Rails developers render and cache collections of partials. -For example, at Inkling we render a client homepage as a collection of divs: +For example, at Inkling we render a client homepage as a collection of divs: ```erb -<%= render :partial => 'markets/market', :collection => @markets %> +<%= render partial: 'markets/market', collection: @markets %> ``` -And each _market.html.erb partial is cached. If you looked inside you'd see something like: +And each _market.html.erb partial is cached. If you looked inside you'd see something like: ```erb <% cache(market) do %> @@ -58,18 +66,18 @@ slow things.... <% end %> ``` -It's tough to cache the entire collection of these partials in a single parent, because each user sees a different homepage depending on their permissions. But even if we could cache the entire page for lots of users, that parent cache would be invalidated each time one of its children changes, which they do, frequently. +It's tough to cache the entire collection of these partials in a single parent, because each user sees a different homepage depending on their permissions. But even if we could cache the entire page for lots of users, that parent cache would be invalidated each time one of its children changes, which they do, frequently. -So for a long time I've dealt with the performance of rendering out pages where we read from Memcached dozens and dozens of times, sequentially. Memcached is fast, but fetching from Memcached like this can add up, especially over a cloud like Heroku. +So for a long time I've dealt with the performance of rendering out pages where we read from Memcached dozens and dozens of times, sequentially. Memcached is fast, but fetching from Memcached like this can add up, especially over a cloud like Heroku. -Luckily, Memcached supports reading a bunch of things at one time. So I've tweaked the render method of Rails to utilize fetching multiple things at once. +Luckily, Memcached supports reading a bunch of things at one time. So I've tweaked the render method of Rails to utilize fetching multiple things at once. How much faster? ----------------------------- -Depends on how many things your fetching from Memcached for a single page. But I tested with [a simple application that renders 50 items to a page](https://github.com/n8/multi_fetch_fragments_test_app). Each of those items is a rendered partial that gets cached to Memcached. +Depends on how many things your fetching from Memcached for a single page. But I tested with [a simple application that renders 50 items to a page](https://github.com/n8/multi_fetch_fragments_test_app). Each of those items is a rendered partial that gets cached to Memcached. -There's two actions: without_gem and with_gem. without_gem performs caching around each individual fragment as it's rendered sequentially. with_gem uses the new ability this gem gives to the render partial method. +There's two actions: without_gem and with_gem. without_gem performs caching around each individual fragment as it's rendered sequentially. with_gem uses the new ability this gem gives to the render partial method. Using [Blitz.io](http://blitz.io) I ran a test ramping up to 25 simultaneous users against the test app hosted on Heroku. I configured Heroku to use 10 dynos and unicorn with 3 workers on each dyno. @@ -94,8 +102,8 @@ Installation 1. Add `gem 'multi_fetch_fragments'` to your Gemfile. 2. Run `bundle install`. -3. Restart your server -4. Render collection of objects with their partial using the new syntax (see above): +3. Restart your server +4. Render collection of objects with their partial using the new syntax (see above): ```erb <%= render partial: 'item', collection: @items, cache: true %>