2013-04-16 79 views
1

我有一些带有has_many关联的Post模型。Rails查询中的自定义排序

class Post < ActiveRecord::Base 
... 
has_many :votes 
has_many :comments 
has_many :ratings 
end 

我想要一个查询命令的帖子由(votes.count + comments.count + ratings.count)。

例如,如果我的帖子有3票,2条评论和1条评价,那么它的排序“度量”的值将是6.我该怎么做?

我也希望是同为3点的参数(投票,评论,评分)下令它第二次查询,而且还增加了一个第四个参数是成反比created_at,所以较新的帖子会被高排名和旧帖子会排名较低。总之,排序度量将类似于:

F*(1/created_at) + votes.count + comments.count + ratings.count),其中F是比例因子。我将如何做到这一点?

回答

3

这是关于算法。

对于非常简单的算法,查询是可以的。当你的想法不断增加时,需要更复杂的方法,查询将不再适合。

我建议你建立一个名为“分数”的字段来存储计算结果。它在创建记录时具有初始值。然后,每次更新其中一个因素 - 投票,评论和评分时,您都会触发一个钩子来再次计算“分数”。

当您的算法发生变化时,您会安排工作人员重新计算所有记录的“分数”。

对于订购,只需简单地按“分数”排序即可。

3

我建议您使用AR counter cache这里:

4.1.2.4:counter_cache

:counter_cache选项可用于使找到属于对象更高效的数量。

尽管在包含belongs_to声明的模型上指定:counter_cache选项,但实际列必须添加到关联的模型中。

所以,你会修改相应belongs_to声明以包括:counter_cache选项:

class Vote < ActiveRecord::Base 
    belongs_to :post, :counter_cache => true 
end 
# Similarly for the other two... 

,然后在迁移中添加计数器列到你的posts表:

def change 
    change_table :posts do |t| 
    t.integer :votes_count 
    #... 
    end 
end 

你”您还需要进行迁移以初始化您现有的Post的计数器。

那么你就必须计数器为您的模型的特性,你可以说这样的话:

Post.where(...).order('posts.votes_count + posts.comments_count + posts.ratings_count') 

如果你想包括created_at那么你可以使用extract(epoch from created_at)获得时间戳作为一个方便的双精度您可以在算术表达式中使用的值。


这样做的缺点是,计数器可以不同步的,如果你迷失而是从一条道路通头发到Rails涅磐(或者其它任何它是真的;),所以你需要小心不要亲自触摸数据库,并始终通过关联来创建和销毁事物。我还建议你建立一个quick'n'dirty健康检查器,你可以随时运行,以确保计数器是正确的。

如果您很高兴能够成为PostgreSQL的特定人员,那么您可以抛弃:counter_cache => true废话和随之而来的所有脆弱性,并使用数据库中的触发器来维护缓存的计数器值。

1

这是需要在数据库中完成的原因吗?如果没有,我会建议你在查找所有记录及其包含的关联后使用sort_by ruby​​方法。例如:

# In the post model 
class Post < ActiveRecord::Base 
    def custom_metric 
    votes.size + comments.size + ratings.size 
    end 
end 

# In post controller 
@posts = Post.where(id: ..).includes(:votes, :comments, :ratings).sort_by(&:custom_metric) 

您可以按照您想排序对象的其他方式使用相同类型的逻辑。这种方法与其他人建议的方法相比会更快,并且会带来不会导致任何数据非规范化的好处。不管数据库的状态如何,查询将始终返回所需的结果。

+1

我同意你在某种程度上的答案,但是如果你想做分页或者只是检索前N个结果,那么从数据库检索所有记录可能会产生问题 – Lummo