Ruby on Rails - NilClass的未定义方法

时间:2014-07-29 23:29:30

标签: ruby-on-rails ruby methods updates rating

我正在创建一个图片评级应用,用户可以点击图片并按照1到5的等级对其进行评分。我试图计算图片的平均评分。在用户点击评级值之前,该值已成为图片的评级。

Rating: 5

如果用户点击1,则评级将更改为1

Rating: 1

实际情况下,评分应为3。

(5 + 1) / 2
=> 3

这是我迄今为止在实现此功能方面所取得的成就。

我添加了一个迁移,为我的图片表创建两个新列

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错误。

这是我在PicturesController中的更新方法。

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

这是我的PicturesController中的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>

4 个答案:

答案 0 :(得分:1)

这将处理属性中未初始化(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:)

答案 1 :(得分: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。

答案 2 :(得分:1)

好吧,它不起作用的主要原因是因为

  • 你拿图片
  • 从数据库中检查stars,然后检查NOT传递的form-parameters
  • 你做update_attributes,如果我没有弄错,用于设置属性然后保存完整的对象,但由于rails 4只更新传递的属性(这是你所期望的)

一句小话:保持评级正确是我将放置在模型中的函数,而不是控制器中的函数。

此外,如何处理如果为nil,初始化为零我写了一篇简短的blogpost。简而言之:否决了吸气剂。

所以我建议以下解决方案。在你的模型中写

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本身将正确处理零或零。很好:我不知道你如何处理“不评级”(零?零?)。可能会添加零评级,因为它会添加评级,但我知道的大多数UI都不允许选择0作为评级,因此您可能希望更改零处理。

答案 3 :(得分:1)

好的,另类......

做一个after_initialize所以字段永远不会永远是零。即使您正在创建一个新的Picture对象,它们也会被初始化为零。问题会消失。

  class Picture << ActiveRecord::Base

    after_initialize do |picture|
      picture.ratings_count ||= 0
      picture.rating_total ||= 0
    end

    ...
  end