我正在创建一个图片评级应用程序,用户可以点击图片并从1到5进行评分。我在计算一部电影的平均评分。在此之前,当用户点击一个评分值时,这个值就变成了图片的评分。
Rating: 5
如果用户点击1,评分将变为1
Rating: 1
在现实中,评级应该是3。
(5 + 1) / 2
=> 3
这是我目前实现这个特性所完成的工作。
我添加了一个迁移来为我的Pictures Table创建两个新列
rails g migration AddRatingsToPictures ratings_count: integer, rating_total: integer
两个新属性ratings_count和rating_total都是整数类型,这意味着它们在默认情况下被分配一个nil值。
p = Picture.first
p.attribute_names
=> ['id', 'title', 'category', 'stars', 'updated_at', 'created_at',
'ratings_count', 'rating_total']
p.ratings_count
=> nil
p.rating_total
=> nil
我唯一的问题是NilClass错误。
这是我的更新方法在我的图片控制器
def update
@picture = Picture.find(params[:id])
@picture.ratings_count = 0 if @picture.stars.nil?
@picture.rating_total = @picture.stars
@picture.rating_total += @picture.stars if @picture.stars_changed?
@picture.ratings_count += 1 if @picture.rating_total_changed?
if @picture.update_attributes(picture_params)
unless current_user.pictures.include?(@picture)
@picture = Picture.find(params[:id])
current_user.pictures << @picture
redirect_to @picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
这是我的picture_param方法在我的图片控制器
def picture_params
params.require(:picture).permit(:title, :category, :genre, :stars)
end
这是两个新列的作用
ratings_count: Calculates the number of times a picture has been rated
rating_total: Calculates the sum of the stars a picture has received
在上面的代码中,如果图片没有评级,我首先将ratings_count设置为0。这意味着这部电影还没有被分级。
然后我需要首先将rating_total设置为图片拥有的星星数。如果用户改变了星级,我会将这些星级添加到rating_total中。如果总数增加了,那就是我增加收视率的提示。
显然,要计算平均值,我会这样做。
(@picture.rating_total / @picture.ratings_count).to_f
现在,我想我有正确的想法,但我知道为什么这不起作用。当用整数值创建列时,默认情况下它们被设置为nil。当我加载网页时,这会导致NilClass错误。
undefined method `/' for nil:NilClass
下面是我在View
中的代码<li><strong>Rating:</strong> <%= pluralize((@picture.rating_total / @picture.ratings_count), 'Star') %></li>
好吧,它不起作用的主要原因是
- 您获取图片
- 检查数据库中的
stars
,而不是通过表单参数 - 你做update_attributes,如果我没有错,用于设置属性,然后保存完整的对象,但由于rails 4只更新传递的属性(这是你所期望的)
一个小注释:保持评级正确是我将放在模型中的功能,而不是放在控制器中。
此外,如何处理如果nil,初始化为零我写了一篇简短的博文。简而言之:否决getter。所以我会提出以下解决方案。在你的模型中写
class Picture < ActiveRecord::Base
def ratings_count
self[:ratings_count] || 0
end
def ratings_total
self[:ratings_total] || 0
end
def add_rating(rating)
return if rating.nil? || rating == 0
self.ratings_count += 1
self.ratings_total += rating
self.stars = self.ratings_total.to_f / self.ratings_count
self.save
end
def rating
return 0 if self.ratings_count == 0
self.ratings_total.to_f / self.ratings_count
end
,然后你的控制器中的代码变得更加清晰和可读:
def update
@picture = Picture.find(params[:id])
stars = picture_params.delete(:stars)
if @picture.update_attributes(picture_params)
@picture.add_rating stars
unless current_user.pictures.include?(@picture)
current_user.pictures << @picture
redirect_to @picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
我首先从参数中删除了:stars
,因为我不想保存它们,我想使用它们作为add_rating
。然后我尝试update_attributes
,如果有任何失败的验证,它将失败,如果这是好的,我将add_rating
,它本身将正确处理nil或零。当然:我不知道你是如何处理"非评级"(nil?零?)。有可能应该添加0的评级,因为它将添加评级,但我知道的大多数UI不允许选择0作为评级,所以您可能想要更改零处理。
这将处理属性中未初始化(nil)值的情况…
def update
@picture = Picture.find(params[:id])
if @picture.stars_changed?
@picture.ratings_count = (@picture.ratings_count || 0) + 1
@picture.rating_total = (@picture.rating_total || 0) + ( @picture.stars || 0)
end
您不需要一个评级数组或持久化到数据库的评级,假设您只计算评级变化的投票,您可以累积计数和总数并将两者除以(实际上,这就是您正在做的,因此我正在向转换)。
虽然在我看来如果我把一张图片从5改成了1而它只变成了3,我就会一直点击1:)
您可以在创建迁移时设置它的默认值。但是不用担心,您可以创建一个新的迁移来更改它:
# Console
rails g migration change_default_for_ratings_count_and_rating_total
# Migration Code
class ChangeDefaultForRatingsCountAndRatingTotal < ActiveRecord::Migration
def change
change_column :pictures, :ratings_count, :integer, default: 0
change_column :pictures, :rating_total, :integer, default: 0
end
end
请记住,有些数据库不会自动将新更新的默认值分配给现有的列条目,所以可能您将不得不遍历每个已经创建的nil值并设置为0的图片。
好的,另一个…
执行after_initialize
,使字段永远不会为nil。即使创建一个新的Picture对象,它们也会初始化为0。问题就会消失。
class Picture << ActiveRecord::Base
after_initialize do |picture|
picture.ratings_count ||= 0
picture.rating_total ||= 0
end
...
end