简体   繁体   中英

Rails 4: small N+1 issue for uncached page

I have a some code that is generating an N+1 database querying issue.

The issue only occurs if the page is uncached. Once the page is cached, adding an .includes actually results in an unnecessary database call. I'm wondering how to get around this problem.

my applicaiton_helper.rb contains the following:

module ApplicationHelper
  def by(article)
    "By #{article.username} on #{article.created_at.strftime('%B %e, %Y')}"
  end
end

my article.rb contains:

class Article < ActiveRecord::Base
  belongs_to :user

  def username
    user.username
  end
end

and my articles_controller.rb contains:

class ArticlesController < ApplicationController
  def index
    @articles = user_signed_in? ? Article.all : Article.all.published.limit(13)
  end
end

The method in question is the username method, which makes a call to the User model. As aforementioned, when the page hasn't already been cached, this results in the by(article) helper method to make continuous calls to the User model without any eager loading. However, since I am caching my views, this inefficiency only occurs once. If I change my articles_controller.rb to the following:

class ArticlesController < ApplicationController
  def index
    @articles = user_signed_in? ? Article.all.includes(:user) : Article.all.published.limit(13).includes(:user)
  end
end

the N+1 issue disappears on the first page load, but then I get an unnecessary .includes upon reloading the page.

Any idea how I can fix this small glitch?

Thanks!

I've just came to a crazy solution: your could check if cached fragment exist in controller. But the problem is that Rails automatically adds a file digest to cache key. So, my "solution": change caching way to smth like this

# in your view:
<% cache 'foo', skip_digest: true do %>
  contents
<% end %>

And then in controller you could check if fragment is already cached:

def index
  if fragment_exist?('asd')
    @articles = user_signed_in? ? Article.all : Article.published.limit(13)
  else
    @articles = user_signed_in? ? Article.all.includes(:user) : Article.published.limit(13).includes(:user)
  end
end

Obviously, turning off digests isn't very good solution: while solving your current problem it adds a new one. But it works :)

I couldn't find a way to get view digest in controller, but anyway I think it would be overkill for such a small problem as one-time-occured N+1. And if what bothers you is only bullet warnings you may turn them off for particular action.

Somehow this has solved my problem:

class Article < ActiveRecord::Base
  belongs_to :user
  delegate :username, to: :user
end

So I simply delegate the username call on an article to the User model. Beautiful, clean and does the trick: bullet no longer complains.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM