has_many与`limit`的关联获取所有记录而不是提供的限制

时间:2017-03-08 12:49:15

标签: ruby-on-rails ruby activerecord associations

我有chat型号:

class Chat < ApplicationRecord
  # Associations
  has_many :chat_recipients
  has_many :recipients, through: :chat_recipients, source: :person
  has_many :messages, dependent: :destroy
  has_many :latest_5_messages, -> { order(created_at: :desc).limit(5) }, class_name: Message.name
  # Associations
end

message模型:

class Message < ApplicationRecord
  # Associations
  belongs_to :person
  belongs_to :chat
  # Associations
end

这两个协会:

has_many :messages, dependent: :destroy
has_many :latest_5_messages, -> { order(created_at: :desc).limit(5) }, class_name: Message.name

指向同一个模型/类。 我期望的是,应该只获取5条消息来减少服务器负载。我已经开发了超过4年的Rails应用程序了,我发现了一些我觉得有些奇怪的东西。当我在rails console上运行此命令时: 2.3.1 :080 > chat = current_person.chats.includes(:recipients, :latest_5_messages).find(1) 这是输出:

Chat Load (0.8ms)  SELECT  "chats".* FROM "chats" INNER JOIN "chat_recipients" ON "chats"."id" = "chat_recipients"."chat_id" WHERE "chat_recipients"."person_id" = $1 AND "chats"."id" = $2 LIMIT $3  [["person_id", 1], ["id", 1], ["LIMIT", 1]]
ChatRecipient Load (0.4ms)  SELECT "chat_recipients".* FROM "chat_recipients" WHERE "chat_recipients"."chat_id" = 1
Person Load (0.7ms)  SELECT "people".* FROM "people" WHERE "people"."id" IN (1, 2)
Message Load (1.0ms)  SELECT "messages".* FROM "messages" WHERE "messages"."chat_id" = 1 ORDER BY "messages"."created_at" DESC

可以清楚地看到,提取了获取所有消息的查询。但是我想只获取5条记录,对吧?如果无论如何取得所有记录,那么对has_many关联应用限制的重点是什么。但这不是现在,我跑了: chat.latest_5_messages.count 这是输出:

2.3.1 :082 > chat.latest_5_messages.count
    (0.9ms)  SELECT COUNT(count_column) FROM (SELECT  1 AS count_column FROM "messages" WHERE "messages"."chat_id" = $1 LIMIT $2) subquery_for_count  [["chat_id", 1], ["LIMIT", 5]]
 => 5 

我是说发生了什么?我喜欢Rails,我绝对喜欢,但我认为在开发超过4年之后,我仍然在追踪ActiveRecord的工作。为什么另一个查询当我chat.latest_5_messages时,我在Associations::CollectionProxy中看到了5条记录,但事情就是当我在这个集合上循环时,有80多次迭代(它提取了与提供的限制(5)相反的所有相关记录)。看看:

2.3.1 :087 > chat.latest_5_messages.map(&:id)
 => [190, 189, 188, 187, 186, 185, 184, 183, 182, 181, 180, 179, 178, 177, 176, 175, 174, 172, 170, 169, 167, 166, 165, 164, 163, 162, 161, 160, 159, 158, 157, 156, 155, 152, 151, 150, 149, 148, 147, 146, 145, 144, 143, 142, 141, 140, 139, 138, 137, 136, 135, 134, 133, 132, 131, 130, 129, 128, 127, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102]

并且我必须chat.latest_5_messages.all来获取我想要的5条记录,但这是以我想避免的另一个数据库查询为代价的。

对不起如果这个问题听起来像是某些人,我可能不知道ActiveRecord的某些部分,但如果有人解释,我们将非常感激。

P.S:这只能使用原始SQL查询来实现,还是有办法以Rails方式实现。

2 个答案:

答案 0 :(得分:0)

听起来您正在尝试加载具有指定限制的关联,而ActiveRecord不支持该关联。见http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Eager+loading+of+associations

特别是:

  

如果您急切加载与指定的:limit选项的关联,它将被忽略,返回所有关联的对象

这也在很多其他地方讨论,例如Rails 4 Eager load limit subqueryRails Eager Load and Limit

我无法解释为什么map会给你所有的记录 - 这肯定是奇怪的。您是否有机会在map上覆盖ApplicationRecord的定义?升级到Rails / ActiveRecord的最新次要版本会对它产生影响吗?

答案 1 :(得分:0)

您的想法的最佳实现是范围

class Chat < ApplicationRecord
  has_many :chat_recipients
  has_many :recipients, through: :chat_recipients, source: :person
  has_many :messages, dependent: :destroy

  scope :latest_5_messages, -> { order(created_at: :desc).limit(5) }
end