Rails 控制器中实例变量值的初始计算错误

Error with initial calculation of instance variable values in Rails controller

我希望构建一个基本应用程序,该应用程序可以根据用户对给定午餐的愉快程度进行评分。

在我看来,投票按钮是这样的:

<%= form_for :lunch, url: upvote_lunch_lunch_path(params[:id]), method: :put, :html => {:class => 'form-inline'} do |f| %>
  <%= f.hidden_field :liked %>
  <%= f.hidden_field :id %>
  <%= f.submit "Like Lunch", class: "btn btn-large btn-success" %>
<% end %>

<%= form_for :lunch, url: downvote_lunch_lunch_path(params[:id]), method: :put, :html => {:class => 'form-inline'} do |f| %>
  <%= f.hidden_field :disliked %>
  <%= f.hidden_field :id %>
  <%= f.submit "Dislike Lunch", class: "btn btn-large btn-danger" %>
<% end %>

显示值(部分)如下所示:

<%= number_to_percentage(@enjoy_score * 100, precision: 0, format: "%n") %>

最后,在我的 "lunches" 控制器中,我有以下内容:

def show
    @lunch = Lunch.find(params[:id])
    @provider = @lunch.provider
    @total_enjoy_votes = (@lunch.liked + @lunch.disliked)
    @enjoy_score = (@lunch.liked.to_f / @total_enjoy_votes)

  end

  def downvote_lunch
    @lunch = Lunch.find(params[:id])
    @lunch.increment!(:disliked)
    redirect_to @lunch
  end

  def upvote_lunch
    @lunch = Lunch.find(params[:id])
    @lunch.increment!(:liked)
    redirect_to @lunch
  end

一切都按预期工作,只要数据库已经有针对特定午餐 ID 的喜欢和不喜欢的值。例如,如果您是第一个尝试回答此问题的人(假设刚创建午餐),应用程序将出错并显示消息“undefined method +' for nil:NilClass" at this line:@total_enjoy_votes = (@ lunch.liked + @lunch.disliked)`

两个问题:

  1. 为什么如果我打开数据库(使用 sqlitebrowser)和 "seed" 喜欢 = 1 和不喜欢 = 1 的一行数据,这些方法将按预期工作,但如果我不这样做,它出错了吗?我应该如何处理 "initialize" @lunch.liked 和 @lunch.disliked 以便初始用户不会出错?
  2. (奖励积分)如何让控制器代码保持干燥,这样我就不必输入 @lunch = Lunch.find(params[:id]) 在每个方法的开头?

非常感谢。如果这是一个非常简单的问题,我深表歉意。

1.

@total_enjoy_votes = (@lunch.liked + @lunch.disliked) 错误,因为 @lunch.likednil,因为它从未设置为任何东西,以及 @lunch.disliked

为避免此错误,您应该检查是否存在喜欢和不喜欢的内容。

liked = @lunch.liked ? @lunch.liked : 0
disliked = @lunch.disliked ? @lunch.disliked : 0
@total_enjoy_votes = (liked + disliked)
@enjoy_score = (liked.to_f / @total_enjoy_votes)

2.

before_filter :find_lunch!, only: [ :update, :destroy ] # list of actions where to perform the method.
private  
 def find_lunch!
   @lunch = Lunch.find(params[:id])
 end

编辑

对行的解释:@lunch.liked ? @lunch.liked : 0

经常派上用场的是ternary operator

语法

boolean_expression ? true_expression : false_expression

例子

grade = 88
status = grade >= 70 ? "pass" : "fail"
#=> pass

是一样的,好像我写的是这样的:

if @lunch.liked.nil?
  liked = 0
else
  liked = @lunch.liked
end

第一种方法:-

def show
  @lunch = Lunch.find(params[:id])
  @provider = @lunch.provider
  liked = ((@lunch.liked.present? and @lunch.liked >= 0) ? @lunch.liked : 0)
  @total_enjoy_votes = liked + ((@lunch.disliked.present? and @lunch.disliked >= 0) ? @lunch.disliked : 0)
  @enjoy_score =  (@total_enjoy_votes > 0 and liked > 0) ? (@lunch.liked.to_f / @total_enjoy_votes) : 0
end

第二种方法:-

Lunch model 中设置回调为

class Lunch <  < ActiveRecord::Base
  before_save :initialize_liked_disliked

  private
  def initialize_liked_disliked
    if self.new_record?
      self.liked = 0
      self.disliked = 0
    end
  end
end

它将用 0 初始化两个字段,然后在控制器中进行更改:

def show
  @lunch = Lunch.find(params[:id])
  @provider = @lunch.provider
  @total_enjoy_votes = @lunch.liked + @lunch.disliked
  @enjoy_score =  (@total_enjoy_votes > 0) ? (@lunch.liked.to_f / @total_enjoy_votes) : 0
end