我们正在构建一个创建类似于 Facebook 的群组页面的群组页面的应用程序。有人可以在页面上发帖,并且该帖子可以有回复。由于它们具有非常相似的属性,因此帖子和回复被合并到同一个 STI 表中
class Page < ActiveRecord::Base
has_many :posts
has_many :replies, through: :posts
end
class BasePost < ActiveRecord::Base
...
end
class Post < BasePost
belongs_to :page
...
end
class Reply < BasePost
belongs_to :post
...
end
我希望能够得到page.posts_and_replies
为了找到页面的“最喜欢的帖子或回复”之类的内容,我们必须将帖子和回复结合起来,以便我们可以获得结果集,例如:
top_messages = page.posts_and_replies.order_by('total_likes DESC').limit(10)
将帖子和回复放在一起很棘手
posts_and_replies
尽管我们通常需要基于单个连接查询它们,但要作为单个结果集进行排序:
class Page < ActiveRecord::Base
has_many :posts
has_many :replies, through: :posts
has_many :posts_and_replies, class_name: 'BasePost'
end
# ideally we could then do queries such as
most_recent_messages = @page.posts_and_replies.order('created_at DESC').limit(10)
我们不能这样做,因为page_id
只存在于posts
. “回复”仅通过他们所属的帖子引用该页面。所以这个加入给了我们帖子而不是回复。
可能的解决方案:
对数据进行非规范化...
我们可以将其复制page_id
到回复和帖子上,但如果可能的话,我真的很想避免这样做,因为非规范化的数据往往会以泪水告终。
使用自定义finder_sql
我们可以使用自定义查找器 sql 来分别获取帖子和回复,然后将它们组合起来:
class Page < ActiveRecord::Base
has_many :posts
has_many :replies
has_many :posts_and_replies, class_name: 'BasePost', finder_sql: Proc.new{
"select base_posts.* from base_posts
where page_id = #{self.id}
UNION
select
base_posts.*
from base_posts left join base_posts as head_posts
on base_posts.post_id = head_posts.id
where head_posts.page_id = #{self.id}"
}
end
自定义 finder_sql 有效,但不适用于关联扩展
上面的 finder_sql 实际上确实有效,但是没有一个关联扩展有效。每当我尝试使用关联扩展(例如.where
)时,它都会退回到内置的 finder_sql:
这有效
Page.find(8).posts_and_replies
=> select base_posts.* from base_posts
where page_id = 8
UNION
select
base_posts.*
from base_posts left join base_posts as head_posts
on base_posts.post_id = head_posts.id
where head_posts.page_id = 8
*然而,这又回到了不正确的 finder_sql*
Page.find(8).posts_and_replies.where('total_likes > 1')
=> select "base_posts".* from "base_posts" where "base_posts"."page_id" = 8
and (total_likes > 12)
由于某种原因,关联扩展没有使用正确的finder_sql
如何让关联扩展尊重声明的 finder_sql?
问题似乎归结为未正确使用 finder_sql 的关联扩展。有什么办法可以强制执行吗?