Rails查询中的自定义排序

时间:2013-04-16 04:05:17

标签: ruby-on-rails ruby-on-rails-3 postgresql activerecord

我有一个带有一些has_many关联的Post模型。

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成反比的第4个参数,因此较新的帖子将排名很高,较旧的职位排名较低。总之,订购指标如下:

F*(1/created_at) + votes.count + comments.count + ratings.count),其中F是比例因子。我该怎么做?

3 个答案:

答案 0 :(得分:3)

这是关于算法的。

对于非常简单的算法,查询是可以的。当您的想法不断增长时,需要更复杂的方法,并且查询将不再适用。

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

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

订购时,只需按“分数”订购即可。

答案 1 :(得分:3)

我建议你在这里使用AR counter cache

  

4.1.2.4:counter_cache

     

:counter_cache选项可用于更有效地查找归属对象的数量   [...]
  虽然在包含:counter_cache声明的模型上指定了belongs_to选项,但必须将实际列添加到关联模型中。

因此,您需要修改相应的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)将时间戳作为方便的双精度值来获取,可以在算术表达式中使用。


这样做的缺点是,如果你迷路了,那么计数器可能会失去同步但是从The One True Path To Rails Nirvana(或者它真的要去的地方)的头发,所以你需要注意不要自己触摸数据库,并始终通过关联来创建和销毁事物。我还建议你建立一个quick'n'dirty理智检查器,你可以不时运行,以确保计数器是正确的。

如果您很高兴能够使用PostgreSQL,那么您可以抛弃:counter_cache => true无意义及其附带的所有脆弱性,并使用数据库中的触发器来维护缓存的计数器值。

答案 2 :(得分: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)

您可以按照您想要对对象进行排序的其他方式使用相同类型的逻辑。这种方法与其他人建议的方法相当,并且不会引起任何数据非规范化。无论数据库的状态如何,查询都将始终返回所需的结果。