如何在Rails中有效地评估和检索模型的平均评级

时间:2014-01-11 00:29:59

标签: ruby-on-rails

我有三个型号,User,Venue,Rating,如下:

class User < ActiveRecord::Base
  has_many :ratings
end

class Venue < ActiveRecord::Base
  has_many :ratings
end

class Rating < ActiveRecord::Base
  belongs_to :user
  belongs_to :venue
end

用户可以对0-5的场地进行评分。用户可以根据需要随时对场地进行评级,并且通常他们在同一场地内的同一用户的评分不同。

我希望能够提供过去一小时的场地平均评分,但我只想考虑该时段内每位用户的单一评分。因此,如果用户在过去一小时内多次对同一场地进行评分,则只会考虑他们最近的评分。

目前我有这个:

class Venue < ActiveRecord::Base
  has_many :ratings

  def past_hour_average
    ratings = self.ratings.where(:created_at => 1.hour.ago..Time.now).uniq_by(&:user_id)
    # loop through each record and compute average
    sum = 0
    ratings.each do |rating|
      sum += rating.value
    end
    return sum / ratings.size
  end
end

然而,这种方法似乎效率低下。每次我想要一个场地的评级我都要计算它。 假设有许多用户经常评价单个场地,那么计算平均评级的更好方法是什么呢?

2 个答案:

答案 0 :(得分:1)

我认为这应该有效:

def past_hour_average
  ratings = self.ratings.where(created_at: 1.hour.ago..Time.now).order(:created_at).group(:user_id)

  ratings.sum(:value) / ratings.count
end

如果从每个用户返回最旧而非最新的评分,您可能只需要撤消订购。

这正是你的代码所做的......它只是让数据库为你做总和,而不是在你的ruby代码中手动计算它。

答案 1 :(得分:1)

您确定此计算的效率甚至会成为一个问题吗?我认为这不太可能,除非您的网站遇到非常非常重读取负载。

但它确实很重要,这是一件你可以做的简单的事情,可能会有所帮助:

 def past_hour_average
   @past_hour_average ||= begin
     # calculation here
   end
 end

这将确保在单个请求的空间内对单个Venue进行多次计算。

如果您需要更好,并且实际上已经检查过以确保确实是一个问题,您可以缓存计算结果并使缓存无效(如果它早于某个)分钟数。我不打算在这里使用MemCached(等)。我会做类似的事情:

 class Venue
   @@avg_rating_cache = {}

   def past_hour_average
     if avg,time = @@avg_rating_cache[self.id] && time > (Time.now - 10.minutes)
       @@avg_rating_cache[self.id] = [avg, Time.now]
       return avg
     end

     value = calculation_here
     @@avg_rating_cache[self.id] = [value, Time.now]
     value
   end
 end

这将直接将结果缓存在每个应用程序进程的内存中(因此访问MemCached缓存不会有额外的开销/延迟)。如果您有超过10,000个场地,则需要从缓存中逐出条目,因为添加了新条目以防止过多的内存使用。