我有三个型号User
、Post
和Favorite
。
class User < ApplicationRecord
has_many :posts
end
class Post < ApplicationRecord
belongs_to :user
end
class Favorite < ApplicationRecord
belongs_to :post
belongs_to :user
end
> user = User.create(name: 'user1')
> user.posts.create(title: 'Title 1')
> user.posts.create(title: 'Title 2')
> Favorite.create(user_id: 1, post_id: 2)
我想检索所有属于user1
的帖子,并由用户保存最喜欢的状态。
> User.first.posts_with_fav_status
=> [#<Post id: 1, title: "Title 1", user_id: 1, faved: false> ],
[#<Post id: 2, title: "Title 2", user_id: 1, faved: true> ]
我该如何编写这样的查询方法?
编辑
我可以通过以下查询获得最喜欢的状态。但是这个查询每次都调用子查询。当数据库变得更大时,速度会太慢。如何重写此查询?
def posts_with_fav_status
posts.select(<<-SQL)
*,
EXISTS(SELECT * FROM favorites
WHERE favorites.user_id = posts.user_id
AND favorites.post_id = posts.id) as faved
SQL
end
我找到了这个解决方案(在Rails 6.1.4.4上测试(
class User < ApplicationRecord
has_many :posts
# User.first.posts_with_faved
def posts_with_faved
posts.select("posts.*, favorites.user_id = #{id} as faved")
.left_joins(:favorites)
end
end
# due to how the inspect works, you won't see the faved attribute in the output, but it is there
irb(main):053:0> first, second = User.first.posts_with_faved
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Post Load (0.2ms) SELECT posts.*, favorites.user_id = 1 as faved FROM "posts" LEFT OUTER JOIN "favorites" ON "favorites"."post_id" = "posts"."id" WHERE "posts"."user_id" = ? [["user_id", 1]]
=> #<ActiveRecord::AssociationRelation [#<Post id: 1, title: "Title 1", user_id: 1, created_at: "2022-01-28 09:34:19.048598000 +0000", updated_at: "2022-01-28 09:34:19.048598000 +0000">, #<Post id: 2, title: "Title 2", user_id: 1, created_at: "2022-01-28 09:34:22.172245000 +0000", ...
irb(main):054:0> first.faved
=> nil
irb(main):055:0> second.faved
=> 1
需要考虑的事项:
- 如果链接查询,请小心不要用
select
重写。否则一切都会崩溃 - 查询不返回布尔值,但几乎返回:
nil
而不是false
,1
而不是true
。如果您将其与if
一起使用,这仍然有效
您错过了User
和Post
模型中的关系:
用户:
class User < ApplicationRecord
has_many :posts
has_many :favorites
has_many :favorite_posts, through: :favorites, source: :post
end
职位:
class Post < ApplicationRecord
belongs_to :user
has_many :favorites
has_many :favorited_by, through: :favorites, source: :user
end
用它你可以写
u = User.first
u.favorite_posts.where(user_id: u.id)
您拥有表和模型的方式我看不出有什么方法可以避免子查询来获得您想要的结果。
不确定你的用例是什么,但我假设这些是用户正在保存的帖子,有些将是收藏夹,因为如果用户是帖子的作者,并且他们喜欢自己的帖子,那么为什么不在posts
表中添加一列,将其指定为收藏夹呢?
如果我的假设是正确的,那么放弃Favorite
模型,转而使用包括user_id
、post_id
和favorite
的UserPosts
loookup模型可能会更好。
Class User < ApplicationRecord
has_many :user_posts
has_many :posts, through: :user_posts
end
Class UserPost < ApplicationRecord
belongs_to :user
belongs_to :post
end
Class Post < ApplicationRecord
has_many :user_posts
has_many :users, through: :user_posts
end
然后你可以使用
user = User.first
user_posts = user.user_posts.eager_load(:posts)
fave
在user_posts
表/模型上,因此它可以由user_posts.first.fave?
和实际发布内容user_posts.first.post
访问。
您可以将accepts_nested_attributes_for :post
添加到UserPost
模型中,并为用户创建新的帖子关联:
User.first.user_posts.create(fave: true, post: {title: 'title text', ...})
除此之外,您还必须使用子查询或第二个查询,然后重新映射到您自己设计的哈希/数组中。