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.