在一个由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
但它破坏了代码
答案 0 :(得分:1)
据我所知,你做不到。自定义关联通常用于允许除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.
答案 1 :(得分:1)
这似乎对我有用,所以请试一试,看看它是否适合你:
Topic.includes(:posts).where("posts.id = (select id from posts where posts.topic_id = topics.id limit 1)").references(:posts)
这将创建一个从属子查询,其中子查询中的posts
topic_id与父查询中的topics
id匹配。使用子查询中的limit 1
子句,结果是每个Topic
行只包含1个匹配的Post
行,由于includes(:post)
而急切加载。
请注意,在将SQL字符串传递给引用预先加载的关系的.where
时,应附加references
方法以通知ActiveRecord我们引用了一个关联,以便它知道在后续查询中执行适当的连接。显然它在没有这种方法的情况下在技术上工作,但是你得到了一个弃用警告,所以你不妨抛弃它,以免在将来的Rails更新中遇到问题。
答案 2 :(得分:0)
在尝试通过Rails“本机”解决此问题时,存在一些问题,这些问题在this question中进行了详细说明。
我们用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)