【问题标题】:Rails: how to query the length of a has_many relationship while caching it?Rails:如何在缓存时查询 has_many 关系的长度?
【发布时间】:2017-10-22 15:39:31
【问题描述】:

给定以下模型:

class Author < ApplicationRecord
  has_many :books
end

如果我跑步

author.books.any?

我使用EXISTS 查询访问了数据库。如果我跑

1000.times { author.books.any? }

结果是 1000 次查询,因为结果没有缓存在内存中 author 对象上。

我处于与上述类似的现实生活中,我在同一个 author 对象上调用(一种使用方法)author.books.any? 数千次。

我想通过在作者对象上缓存结果来避免过多的查询。最有表现力的方法是什么?我知道例如author.books.to_a.any? 会起作用,但它不是语义的。 (它还缓存所有关联数据,而不仅仅是关联数据的长度,但这对我来说没问题,因为在我的情况下,关联表非常小。)

ActiveRecord::Relation 上的 any?exists? 是否有内置替代方案可以满足我的要求?

【问题讨论】:

  • 您可以创建查询,它将获取books.count > 0的作者

标签: ruby-on-rails ruby


【解决方案1】:

这通常称为 N+1 查询问题。

如果您急切地使用 includeseager_load 加载关联,则不会创建计数或存在查询:

author = Author.includes(:books).first
author.books.any?

这是因为any? 使用了.size,它足够聪明,可以判断关联已加载。因此它可以在集合上使用.length,而不是查询数据库。

size, length and count in Rails

如果您经常需要不加入的关联计数,您可以在模型上定义计数器缓存。

class Book < ApplicationRecord
  belongs_to :author, counter_cache: true
end
class Author < ApplicationRecord
  has_many :books
end

如果有很多关联记录并且将它们加载到内存中会出现问题,这可能会很有用。

您还可以通过将 select 与 join 结合使用来获取关联的计数:

authors = Author.select('authors.*, COUNT(books.*) AS authors.number_of_books')
      .left_joins(:books) 
      .order('authors.number_of_books')

# just an example 
authors.map { |a| [a.name, a.number_of_books] }

这给出了一个更准确的数字,因为计数器缓存中的值可能是陈旧的。

【讨论】:

  • .left_joins 是 Rails 5 中的新功能。blog.bigbinary.com/2016/03/24/…
  • 我知道如何解决这种预加载问题,以及关于counter_cache。我想知道的是——to_a.any? 有语义上的替代品吗?
【解决方案2】:

是的,您完全可以这样做,但您需要使用新查询重新加载作者对象:

author = Author.where(id: author.id).includes(:books).first

这会加载子记录,如下所示:

author.books.loaded?
#=> true

您现在可以执行author.books.any? 甚至author.books.map(&amp;:field_name) 并且不会运行新的查询。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多