Rails: fighting N+1 query problem

This problem is well known and have been solved 1000 times before. But most articles on rails and N+1 problem address mostly includes function. There are actually some more techniques (much simpler, but not obvious to fight this problem). I want to list them all.

Example 1

We have following models

class Company < ActiveRecord::Base
  has_many :workers
end

class Worker < ActiveRecord::Base
  belongs_to :company
end

And following decorator:

class WorkerDecorator < Draper::Decorator
  def to_link
    h.link_to(name, h.company_worker_path(id: object.company.id, worker_id: object.id))
  end
end

I’m using draper for this decorator. For other options see my post on decorators.

And view

<% decorated_workers.each do |worker| %>
  <li><%= worker.to_link %></li>
<% end %>

This will produce N+1 problem. Simplest fix would be to use company_id directly instead of fetching company record.

class WorkerDecorator < Draper::Decorator
  def to_link
    h.link_to(name, h.company_worker_path(id: object.company_id, worker_id: object.id))
  end
end

Example 2

The same code as previous, but we need to show company name for each worker.

class WorkerDecorator < Draper::Decorator
  def to_link
    h.link_to("#{name} (#{object.company.name})", h.company_worker_path(id: object.company.id, worker_id: object.id))
  end
end

This problem can be solved by preloading company records all at once.

workers = Worker.all.includes(:company)

Also read about differences between includes, preload and eager_load.

Example 3

We need to show company dashboard. So we want to retrieve company and all its workers from database.

company = Company.find(params[:id])
workers = company.workers

We know that every worker on this page have the same company.

First option: use inverse_of, so that worker.company would return the same company from which workers were fetched.

class Company < ActiveRecord::Base
  has_many :workers, inverse_of: :company
end

Second option: use the same instance of company everywhere explicitly.

class WorkerDecorator < Draper::Decorator
  def to_link(company)
    h.link_to("#{name} (#{company.name})", h.company_worker_path(id: company.id, worker_id: object.id))
  end
end

Example 4

We need to show companies list with latest 3 workers hired.

Technique to optimize that kind of cases well explained in this thoughtbot article.

See also