目前我正在开发一个小型图书评级应用,用户可以对图书进行评分和评论。
当然我有一本书模型:
class Book < ActiveRecord::Base
has_many :ratings
end
和评级模型:
class Rating < ActiveRecord::Base
belongs_to :book
end
评级对象的“总评级值”由不同的评级类别(例如,可读性,......)计算。此外,一本书的总评分应按所有给定的评级计算。
现在我问自己的问题:我是否应该计算/查询每个人访问我页面的每一本书的整体评分,还是应该在我的图书模型中添加一个字段,其中(定期)计算并保存整体评分? / p>
编辑:我在这种情况下使用的“计算”是一个简单的平均判断。
示例:一本书有大约200个评级。每个评级都是10个类别评级的组合。所以我想确定一个评级的平均值,并在所有200个评级中结束。
答案 0 :(得分:2)
如果这些评级的平均值在计算上并不昂贵(即不需要很长时间),那么只需动态计算即可。这符合不过早使用的想法(见http://c2.com/cgi/wiki?PrematureOptimization)。
但是,如果您确实希望优化此计算,然后将其存储在书籍模型上并更新评级写入的计算,那么就可以了。这被称为“缓存”结果。以下是一些缓存数据库中平均评级的代码。 (还有其他缓存方式)。
class Book < ActiveRecord::Base
has_many :ratings, after_add :update_average_rating
def update_average_rating
update_attribute(:average_rating, average_rating)
end
def average_rating
rating_sum / ratings.count
end
def rating_sum
ratings.reduce(0) {|sum, rating|
sum + rating.value # assuming rating model has a value attribute
}
end
end
class Rating < ActiveRecord::Base
belongs_to :book
end
注意:上面的代码假定数据库中的book表中存在average_rating
列。请务必使用迁移添加此列。
答案 1 :(得分:2)
<强> DB 强>
最有效(尽管不是传统)方式是使用数据库级ALIAS
列,允许您计算每个{{1}的评级AVG
或SUM
呼叫:
book
这将允许:
#app/models/book.rb
class Book < ActiveRecord::Base
def reviews_avg category
cat = category ? "AND `category` = \"#{category}\"" : ""
sql = "SELECT AVG(`rating`) FROM `reviews` WHERE `book_id` = #{self.id} #{cat})
results = ActiveRecord::Base.connection.execute(sql)
results.first.first.to_f
end
end
这是最有效的,因为它完全由DB处理:
<强>滑轨强>
你应该使用Rails的average
功能:
@book = Book.find x
@book.reviews_avg # -> 3.5
@book.reviews_avg "readability" # -> 5
通过以上操作,您可以调用#app/models/book.rb
class Book < ActiveRecord::Base
has_many :ratings do
def average category
if category
where(category: category).average(:rating)
else
average(:rating)
end
end
end
end
的实例,并评估@book
或average
的评分:
total
-
您还可以在@book = Book.find x
@book.reviews.average #-> 3.5
@book.reviews.average "readability" #-> 5
上使用class method
/ scope
:
Review
这将允许您致电:
#app/models.review.rb
class Review < ActiveRecord::Base
scope :avg, (category) -> { where(category: category).average(:rating) }
end
关联扩展程序
另一种方式(未经测试)是在ActiveRecord Association Extension
中使用@book = Book.find x
@book.reviews.avg #-> 3.5
@book.reviews.avg "readability" #-> 5
对象。
虽然效率不如数据库级别的查询,但它可以让您在内存中执行活动:
proxy_association.target
这将允许您致电:
#app/models/book.rb
class Book < ActiveRecord::Base
has_many :reviews do
def avg category
associative_array = proxy_association.target
associative_array = associative_array.select{|key, hash| hash["category"] == category } if category
ratings = associative_array.map { |a| a["rating"] }
ratings.inject(:+) / associative_array.size #-> 35/5 = 7
end
end
end
答案 2 :(得分:1)
根本不需要重新计算每次访问页面的平均总体评分,因为它只会在有人对该书进行评分时发生变化。所以只需使用字段AVG_RATING或类似的东西,并更新每个给定评级的值。
答案 3 :(得分:1)
您是否考虑过使用评级的缓存版本?
rating = Rails.cache.fetch("book_#{id}_rating", expires_in: 5.minutes) do
do the actual rating calculation here
end
答案 4 :(得分:1)
在大多数情况下,您只需查询数据库即可获得平均值:
average = book.reviews.average(:rating)
在大多数情况下,每次请求查询都不会太昂贵,这将成为一个真正的问题 - 而且,过早优化可能会浪费时间和资源,正如Neil Atkinson指出的那样。
然而,当计算成本成为问题时,有几种方法需要考虑,这取决于计算数据的性质。
如果计算出的数据是值得的资源,则可以将其保存在数据库中。例如,定期生成的报告(每日,每月,每年)以及需要查询的报告。
否则,如果计算出的数据具有较高的“流失率”(每天都会创建很多评论),您可以使用缓存来尽可能避免昂贵的查询,但将数据填充到数据库中可能会导致过多的缓慢UPDATE查询并绑定您的网络或工作流程。
有许多缓存方法相互补充:
etags
利用客户端缓存 - 如果响应没有改变,请不要重新渲染。有关详细信息,请参阅Caching with Rails: An overview。