Rails
ActiveRecord

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

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