Pet*_*xey 5 activerecord finder-sql sti ruby-on-rails-3.2
我们正在构建一个应用程序来创建类似于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
Run Code Online (Sandbox Code Playgroud)
page.posts_and_replies为了找到页面的"最喜欢的帖子或回复"之类的内容,我们必须将帖子和回复结合起来,以便我们可以获得结果集,例如:
top_messages = page.posts_and_replies.order_by('total_likes DESC').limit(10)
Run Code Online (Sandbox Code Playgroud)
要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)
Run Code Online (Sandbox Code Playgroud)
我们不能这样做,因为page_id只存在posts.'回复'仅通过他们所属的帖子引用该页面.所以这次加入给了我们帖子而不是回复.
我们可以复制page_id到回复以及帖子,但我真的想避免这样做,如果可能的话,因为非规范化数据往往以泪水结束.
finder_sql我们可以使用自定义finder 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
Run Code Online (Sandbox Code Playgroud)
上面的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
Run Code Online (Sandbox Code Playgroud)
*但是这会回到错误的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)
Run Code Online (Sandbox Code Playgroud)
由于某种原因,关联扩展不使用正确的 finder_sql
问题似乎已经归结为关联扩展没有正确使用finder_sql.有没有办法强制执行此操作?
最简单的方法可能是使用一些命名范围,并完全避免关联。这是一种稍微有点老套的方法,但可能会给你你正在寻找的东西 - 而且我不确定 Rails 关联是否真的会给你你想要的行为。另外,请注意下面我将使用 ActiveRecord 的安全参数插值 - 您永远不应该自己将字符串插值到 SQL 语句中 - 它会在您不注意时召唤邪灵来侵扰您的数据库。
首先,您将在 BasePost 模型中定义一个命名范围,该范围执行以下操作:
class BasePost < ActiveRecord::Base
scope :popular_posts_for_page, lambda { |page_id|
where("base_posts.page_id = ? OR parent_posts.page_id = ?", page_id, page_id).
joins("left outer join base_posts parent_posts on parent_posts.id = base_posts.post_id").
order("total_likes DESC")
}
end
Run Code Online (Sandbox Code Playgroud)
这里的另一个好处是,您还不会返回实际的列表,而只会返回范围代理。如果您愿意,您现在可以将您的限制或您想要的任何声明链接到该限制上。
如果您希望它可以在您的页面模型上访问,一种方法是:
class Page < ActiveRecord::Base
def popular_posts_and_replies
BasePost.popular_posts_for_page(self.id)
end
end
Run Code Online (Sandbox Code Playgroud)
由于您之前将其定义为命名范围,并且该范围将返回一个范围对象,而不是实际的结果列表 - 它将非常接近关联,以至于差异应该是不可见的。例如:
my_page.popular_posts_and_replies.limit(10)
Run Code Online (Sandbox Code Playgroud)
应该完全按照您的预期行事。
对于我的 SQL 中的任何错误,我深表歉意。
| 归档时间: |
|
| 查看次数: |
223 次 |
| 最近记录: |