LoginSignup
22
13

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-10-06

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

# 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はクエリの結果として出てこないので注意。

22
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
13