Help us understand the problem. What is going on with this article?

ActiveRecordでincludesしているのにキャッシュが効かない場合

More than 3 years have passed since last update.

キャッシュが効かないとき

# app/model/category.rb
class Category < ApplicationRecord
  has_many :books
end

# app/model/book
class Book < ApplicationRecord
  scope :published, -> { where(published: true) }
  belongs_to :category
end
# app/controller/books_controller

def index
  @category = Category.All.includes(:books)
end

# app/views/books/index.html.slim

- @categories.each do |category|
  h1
    = category.name
  table
  - category.books.published.each do |book|
    tr
      td = book.name
      td = book.author

このように、せっかくincludes(:book)をしても、category.books.publishedや、category.books.where(published: true)category.books.order_by(id: :desc)のように、子のモデルにクエリを付け足してしまうとキャッシュが効かず、N+1問題が発生してしまう。

どうするか

# app/model/category.rb
class Category < ApplicationRecord
  has_many :books
  has_many :published_books, -> { published }, class_name: 'Book'
end

# app/model/book
class Book < ApplicationRecord
  scope :published, -> { where(published: true) }
  belongs_to :category
end
# app/controller/books_controller

def index
  @category = Category.All.includes(:published_books)
end

# app/views/books/index.html.slim

- @categories.each do |category|
  h1
    = category.name
  table
  - category.published_books.each do |book|
    tr
      td = book.name
      td = book.author

has_many :published_books, -> { published }, class_name: 'Book'のように、has_manyにスコープを渡してやり、それをincludesの引数に渡すと、キャッシュが効いてくれる。

備考

ActiveRecord::QueryMethodsのコメントには、

    # === conditions
    #
    # If you want to add conditions to your included models you'll have
    # to explicitly reference them. For example:
    #
    #   User.includes(:posts).where('posts.name = ?', 'example')
    #
    # Will throw an error, but this will work:
    #
    #   User.includes(:posts).where('posts.name = ?', 'example').references(:posts)
    #
    # Note that +includes+ works with association names while +references+ needs
    # the actual table name.

とあるので、
Category.includes(:books).merge(Book.published).references(:books)としてもよい。

なお、どちらの場合においても、Bookのスコープの条件が、親のモデル(Category)になるため、上の例ではpublishedなbookが一つもないcategoryはクエリの結果として出てこないので注意。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした