Rails 3:按照从另一个表计算的评级对ActiveRelation进行排序

时间:2011-08-30 11:32:53

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

我的评分系统非常简单,无论是用户喜欢还是他不喜欢这篇文章。

基本上,我有类似的东西,它完美无缺:

class Article
  has_many :ratings
  def self.by_ratings
    all.sort_by{|article| (article.ratings.where(like: true).size).to_f / (article.ratings.size) }
  end
end

你可以猜到,我们的应用程序在数据库中变得庞大,流量增加,这部分代码成为瓶颈。

我试图在纯sql中重写它以提高性能。我也试图将它作为ActiveRelation与其他条件链接起来。尝试也写sql但没有成功。有什么想法吗?

谢谢!

使用:

  • Rails 3.0.10
  • ActiveRecord
  • Sqlite 3(我们可以切换到Postgresql)

4 个答案:

答案 0 :(得分:2)

您需要的是缓存列。

1)创建迁移以支持缓存列:

class AddRatingCacheToArticles < ActiveRecord::Migration

  def self.up
    add_column :articles, :rating_cache, :decimal, :default => 0.0
  end

  def self.down
    remove_column :articles, :rating_cache
  end

end

2)在Article中定义一个更新方法,用于计算:

class Article < ActiveRecord::Base
  has_many :ratings

  def update_rating_cache
    current_rating = ratings.where(:like => true).count.to_f/ratings.count.to_f
    update_attribute(:rating_cache, current_rating)
  end
end

3)在Rating设置回调以在保存时触发update_rating_cache方法:

class Rating < ActiveRecord::Base
  belongs_to :article

  after_save :update_article_rating_cache
  after_destroy :update_article_rating_cache

  def update_article_rating_cache
    article.update_rating_cache if article
  end
end

4)现在通过评级对文章进行排序非常容易:

class Article < ActiveRecord::Base
  has_many :ratings

  def self.by_ratings
    order('rating_cache DESC')
  end

  def update_rating_cache
    current_rating = ratings.where(:like => true).count.to_f/ratings.count.to_f
    update_attribute(:rating_cache, current_rating)
  end
end

这可以用作ActiveRelation!

祝你好运:)

答案 1 :(得分:1)

我还没能在你的模型中测试这个SQL,但你可以尝试一下:

select articles_ratings.*, likes/total rating from (select articles.*, SUM(if(articles.like, 1, 0)) likes, count(*) total from articles JOIN ratings on article_id = articles.id GROUP BY articles.id) articles_ratings ORDER BY rating desc

这应该有希望给你一个文章列表,按其评级(从最高到最低)排序。如果可行,我可以尝试跟进一些导轨。

编辑正如@socjopa所暗示的那样,如果您不想立即将其投入生产,我的下一个建议是将此查询移至视图中。像对待任何其他ActiveRecord一样对待它,并相应地将其与您的文章相关联。

使用适当的索引,视图应该相当快,但每次运行时计算评级值可能不是必需的。如果性能不在您需要的位置,您可能还需要考虑在您的文章表中存储评级列。只要修改或创建文章评级,您就可以简单地更新此评级。

也就是说,这个表现应该是当前迭代的日夜。

答案 2 :(得分:1)

从您的by_ratings方法中,我了解您希望文章按最喜欢的评论/评分排序。

我们可以将方法重写为这样的范围:

scope :by_ratings, select('articles.*, ((select count(id) from ratings where article_id = articles.id) - count(article_id) ) as article_diff_count')
 .joins(:ratings).group('article_id').where('like = ?',true).order('article_diff_count asc')

我选择比较总评分和喜欢评分之间的difference instead of ratio,因为这应该在SQL引擎上更轻。希望这会有所帮助。

答案 3 :(得分:1)

我对你的实现做了一些假设,就像我假设你的评级模型中有value字段,对于“喜欢”可以是1,对于“不喜欢”可以是-1,等等

开始于:

class Article
   has_one :rating_value, :class_name => 'RatingValue'
end

class RatingValue < ActiveRecord::Base
   belongs_to :article
   set_table_name "rating_values"
end

因此,在迁移中生成视图(postgres示例):

   execute %q{CREATE OR REPLACE VIEW rating_values AS
 SELECT ratings.article_id, sum(ratings.value) AS value
   FROM ratings
  GROUP BY ratings.article_id;}

如果您有这样的数据库视图,则可以创建排序所需的范围:

scope :ascend_by_rating, {:joins => %Q{
LEFT JOIN "rating_values"
ON rating_values.article_id = article.id },
                                          :order => "rating_values.value ASC"}

应该比在Ruby中的排序尝试更有效率