2

在一个由 Rails 应用程序制作的非常简单的论坛中,我在索引操作中从数据库中获取 30 个主题,如下所示

def index

@topics = Topic.all.page(params[:page]).per_page(30)

end 

但是,当我在views/topics/index.html.erb 中列出它们时,我还希望能够访问每个主题中的第一篇文章以显示在工具提示中,这样当用户滚动时,他们可以阅读第一篇文章无需点击链接。因此,在索引中每个帖子的链接中,我将以下内容添加到数据属性中

topic.posts.first.body

每个链接看起来像这样

<%= link_to simple_format(topic.name), posts_path(
:topic_id => topic), :data => { :toggle => 'tooltip', :placement => 'top', :'original-title' => "#{ topic.posts.first.body }"}, :class => 'tool' %>

虽然这很好用,但我担心这是一个 n+1 查询,即如果有 30 个主题,它会执行 30 次

 User Load (0.8ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
  Post Load (0.4ms)  SELECT "posts".* FROM "posts" WHERE "posts"."topic_id" = $1 ORDER BY "posts"."id" ASC LIMIT 1  [["topic_id", 7]]

我注意到 Rails 对其中一些进行了自动缓存,但我认为可能有一种方法可以以不同的方式编写索引操作以避免一些这种 n+1 问题,但我可以弄清楚如何。我发现我可以

include(:posts) 

急切地加载帖子,像这样

@topics = Topic.all.page(params[:page]).per_page(30).includes(:posts)

但是,如果我知道我只想要每个主题的第一篇文章,有没有办法指定?如果一个主题有 30 个帖子,我不想急切地加载所有帖子。

我试着做

.includes(:posts).first

但它破坏了代码

4

3 回答 3

1

据我所知,你不能。自定义关联通常用于允许包含 except 的条件limit

如果您使用指定的 :limit 选项急切加载关联,它将被忽略,并返回所有关联的对象。http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html

class Picture < ActiveRecord::Base
  has_many :most_recent_comments, -> { order('id DESC').limit(10) }, 
                                 class_name: 'Comment'
end

Picture.includes(:most_recent_comments).first.most_recent_comments 
# => returns all associated comments.
于 2013-10-14T05:21:45.370 回答
1

这似乎对我有用,所以试一试,看看它是否适合你:

Topic.includes(:posts).where("posts.id = (select id from posts where posts.topic_id = topics.id limit 1)").references(:posts)

这将创建一个依赖子查询,其中子查询中的poststopic_id 与topics父查询中的 id 匹配。使用limit 1子查询中的子句,结果是每Topic行将只包含 1 个匹配Post的行,由于includes(:post).

请注意,当将 SQL 字符串传递给.where引用急切加载的关系时,references应附加该方法以通知 ActiveRecord 我们正在引用关联,以便它知道在后续查询中执行适当的连接。显然它在技术上可以在没有这种方法的情况下工作,但是你会收到一个弃用警告,所以你最好把它扔进去,以免在以后的 Rails 更新中遇到问题。

于 2013-10-14T05:22:32.050 回答
1

尝试通过 Rails 以“本机”方式解决此问题时会遇到一些问题,详见此问题

我们使用 SQL 范围解决了它,对于您的情况,例如:

class Topic < ApplicationRecord
  has_one :first_post, class_name: "Post", primary_key: :first_post_id, foreign_key: :id

  scope :with_first_post, lambda {
    select(
      "topics.*,
      (
        SELECT id as first_post_id
        FROM posts
        WHERE topic_id = topics.id
        ORDER BY id asc
        LIMIT 1
      )"
    )
  }
end

Topic.with_first_post.includes(:first_post)
于 2019-06-13T12:35:18.283 回答