Vincent is Coding
March 24th, 2024

Avoiding N+1 queries on a model with memoization

Ruby on Rails

During the Scribbles request cycle, there are multiple checks to see if a domain exists on a given blog, and if so, tweak the URL of certain links and also posts.

The problem I had is that when I render out a post, in a list, it would check if the blog had a valid domain to it. And because I am silly, I didn't think much about it until I've seen the database query where it would just do this check many times — obviously.

Here was my initial take on checking if I a blog has_valid_hostname?:

def has_valid_hostname?
  domains.any? && domains.first.is_active?
end

First it checks if I have any domains associated to the blog, and if so grab the first one and see if it's active. The active check is automated and starts with false when you first set up the domain, and then it will check in the background if it's pointing to Scribbles, and your blog.

I could perhaps use a counter cache here, but don't want to complicate things.

Also notice that I use domains.first — the way I designed it is that I will allow multiple domains, and it will grab the one that is set as default, however that will be for another day and I'll add a scope for this. Right now that works and is what I want.

I probably could do without the domains.any? and just optionally chain the last query...

Anyway, this was getting called many times in the same request, which was stupid. Each post list item would call this method, not to mention multiple times just for setting extra bits in the head component of the blog.

So what's the best way to solve that? Enter memoization. Basically store the value once in the same request and then I can re-use it within that request. Here it is:

def has_valid_hostname?
  @has_valid_hostname ||= domains.any? && domains.first.is_active?
end

Basically now I set @has_valid_hostname once only if it doesn't exist. Which basically just means once.

Instead of querying the database 3 million times... ok, more like around 30 times, it does it once in the request.

A nice simple result — and I have no idea why I didn't think of that earlier.