(Ab)using memoize to quickly solve tricky n+1 problems

Usually, discovering n+1 problems in your Rails application that can’t be fixed with an :include statement means lots of changes to your views. Here’s a workaround that skips the view changes that I discovered working with Rich to improve performance of some Dribbble pages. It uses memoize to convince your n model instances that they already have all the information needed to render the page.

While simple belongs_to relationships are easy to fix with :include, lets take a look at a concrete example where that won’t work:

class User < ActiveRecord::Base
has_many :likes

class Item < ActiveRecord::Base
has_many :likes
def liked_by?(user)

class Like < ActiveRecord::Base
belongs_to :user
belongs_to :item

A view presenting a set of items that called Item#liked_by? would be an n+1 problem that wouldn’t be well solved by :include. Instead, we’d have to come up with a query to get the Likes for the set of items by this user:


Then we’d have to store that in a controller instance variable, and change all the views that called item.liked_by?(user) to access the instance variable instead.

Active Support’s memoize functionality stores the results of function calls so they’re only evaluated once. What if we could trick the method into thinking it’s already been called? We can do just that by writing data into the instance variables that memoize uses to save results on each of the model instances. First, we memoize liked_by:

memoize :liked_by?

Then bulk load the relevant likes and stash them into memoize’s internal state:

def precompute_data(items, user)
likes = Like.of_item(items).by_user(user).index_by {|like| like.item_id}
items.each do |item|

The write_memo method is implemented as follows.

def write_memo(method, return_value, args=nil)
ivar = ActiveSupport::Memoizable.memoized_ivar_for(method)
if args
if hash = instance_variable_get(ivar)
hash[Array(args)] = return_value
instance_variable_set(ivar, {Array(args) => return_value})
instance_variable_set(ivar, [return_value])

This problem described here could be solved with some crafty left joins added to the query that fetched the items in the first place, but when there’s several different hard to prefetch properties, such a query would likely become unmanageable, if not terribly slow.

One thought on “(Ab)using memoize to quickly solve tricky n+1 problems”

  1. Nice article, thanks for describing the technique.

    I’ve been doing something similar (well, similar problem) using ivar ‘caching’ (not sure what to call this!) in the method calls, i.e.:

    def something_heavy(opts)
    @something_heavy ||= the_heavy_method(opts)

    Are there any pitfalls (practical / performance) using either approach you think?

Leave a Reply

Your email address will not be published.