【问题标题】:Ruby on Rails - Undefined methods for NilClassRuby on Rails - NilClass 的未定义方法
【发布时间】:2014-09-21 11:43:43
【问题描述】:

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

Rating: 5

如果用户点击 1,评分将变为 1

Rating: 1

在现实中,评分应该是 3。

(5 + 1) / 2
=> 3

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

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

rails g migration AddRatingsToPictures ratings_count: integer, rating_total: integer

新的属性 rating_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 中的图片参数方法

 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

在上面的代码中,如果图片没有评分,我首先将 rating_count 设置为 0。这意味着该图片尚未评级。

然后我需要最初将 rating_total 设置为图片的星数。如果用户更改了星级,我会将这些星级添加到 rating_total。如果总数增加,这就是我增加收视率的提示。

显然,要计算平均值,我会这样做。

(@picture.rating_total / @picture.ratings_count).to_f

现在,我认为我的想法是正确的,但我知道为什么这不起作用。当使用整数值创建列时,默认情况下它们设置为 nil。当我加载网页时,这会导致 NilClass 错误。

undefined method `/' for nil:NilClass

这是我在视图中的代码

<li><strong>Rating:</strong> <%= pluralize((@picture.rating_total / @picture.ratings_count), 'Star') %></li>

【问题讨论】:

  • 如果你想计算一个平均值,你要么必须: 1) 保存一个评级数组,然后计算 - 即(@picture.star_ratings.inject(0) { |total, v| total = total + v } / @picture.star_ratings.count)(或者你可以使用 SQL也计算一下)2)在数据库中分别保存“总评分数”和“总评分数”。@picture.rating_total / @picture.rating_count
  • @JustinAiken 这应该是一个答案

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


【解决方案1】:

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

  • 你去拿图片
  • 您从数据库中检查stars,而不是通过的表单参数
  • 您执行 update_attributes,如果我没记错的话,它用于设置属性然后保存完整的对象,但由于 rails 4 只更新传递的属性(这是您所期望的)

一个小评论:保持评级正确是我将放置在模型中的功能,而不是在控制器中。

此外,如何处理如果为零,初始化为零我写了一个简短的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 作为评级,因此您可能需要更改零处理。

【讨论】:

  • 谢谢,这解决了我的问题。这是一个很好的解决方案。
【解决方案2】:

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

【讨论】:

  • 请注意,如果您不想对同一用户进行两次计数(他点击替换了他之前的投票),那么您需要将投票保存到表格中。
  • 感谢您的回复。看起来我仍然收到相同的 NilClass 错误。也许我需要将此代码嵌套在 if update_attributes 块中?
  • 否,因为您需要在编写字段之前更新它们。哪一行(确切地说)你得到了错误?
  • 我在我的 Pictures#Show 方法的 10 个中得到了这条线。我帖子的最后一行代码。错误来自它看起来的视图。我尝试在控制台上对其进行测试,并更改变量 p 的星星值,代表图片选项。当我同时检查 p.ratings_count 和 p.rating_total 时,它仍然返回 nil。
【解决方案3】:

您可以在创建迁移时设置迁移的默认值。不过不用担心,您可以创建一个新的迁移来更改它:

# 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 的每张图片。

【讨论】:

  • 完美,我从来不知道默认选项。但是你的代码给了我一个问题。在迁移文件中,迁移的名称不应该是 CamelCase 吗?我正在阅读 RubyonRails 指南,这似乎是他们使用的约定。虽然如果您更改现有迁移可能会有所不同?
  • 生成迁移时,这两个操作将导致相同的结果。顺便跟上指南,很好:)
【解决方案4】:

好的,另一种选择......

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

  class Picture << ActiveRecord::Base

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

    ...
  end

【讨论】:

    猜你喜欢
    • 2017-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多