Stan Blog

一些筆記的記錄

[Rails] 使用 merge 合併查詢子句

在寫 rails query 時, 常常會寫出這樣的句子

User.joins([posts: :likes]).where('posts.created_at': 30.days.ago..Time.current).order("posts.id DESC")

如果條件越來越複雜, query 會寫得越來越長

這時候可以使用 ActiveRecord::SpawnMethodsmerge 來整理 query

假設我們有三個 tables

class User < ApplicationRecord
  has_many :posts
  has_many :likes
  has_many :liked_posts, through: :likes, source: :post
end
class Post < ApplicationRecord
  belongs_to :user
  has_many :likes, dependent: :destroy
  has_many :liked_users, through: :likes, source: :user
end
class Like < ApplicationRecord
  belongs_to :user
  belongs_to :post
end

我們想要找出 post 被按了最多 like 的 user 列表

這是一般的寫法

User.joins([posts: :likes]).group(:id).order("count(posts.id) DESC")

會跑出這樣的 SQL

SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "likes" ON "likes"."post_id" = "posts"."id" GROUP BY "users"."id" ORDER BY count(posts.id) DESC

可以用 merge 這樣整理

# 先找出按讚數最多的文章,並排序
by_likes_count = Post.joins(:likes).group(:id).order("count(posts.id) DESC")

# 然後用 User 直接 merge
User.joins(:posts).merge(by_likes_count)

這時候跑出的 SQL 會是

SELECT "users".* FROM "users" INNER JOIN "posts" ON "posts"."user_id" = "users"."id" INNER JOIN "likes" ON "likes"."post_id" = "posts"."id" GROUP BY "users"."id" ORDER BY count(posts.id) DESC

與上面直接寫 query 的版本,轉換後的 SQL query 是一樣的

如果還想整理得更漂亮, 可以拆進 model scope, 再去 merge scope

# Post model
scope :sort_by_likes_count, -> { joins(:likes).group(:id).order("count(posts.id) DESC") }

User.joins(:posts).merge(Post.sort_by_likes_count)

Ref: ActiveRecord::SpawnMethods

Comments

comments powered by Disqus