数据库
最有效(虽然不是常规)的方法是使用 db 级别的 ALIAS 列,允许您计算每个 book 调用的评级的 AVG 或 SUM:
#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
这将允许:
@book = Book.find x
@book.reviews_avg # -> 3.5
@book.reviews_avg "readability" # -> 5
这是最有效的,因为它完全由 DB 处理:
导轨
您应该使用 Rails 的 average 功能:
#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
--
您也可以在Review 上使用class method / scope:
#app/models.review.rb
class Review < ActiveRecord::Base
scope :avg, (category) -> { where(category: category).average(:rating) }
end
这将允许您调用:
@book = Book.find x
@book.reviews.avg #-> 3.5
@book.reviews.avg "readability" #-> 5
关联扩展
另一种方法(未测试)是在ActiveRecord Association Extension 中使用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
这将允许您调用:
@book = Book.find x
@book.reviews.avg # -> 3.5
@book.reviews.avg "readability" # -> 5