【问题标题】:Rails: most Quiz attributes passing to params, apart from user_id. Why?Rails:除了 user_id 之外,大多数 Quiz 属性都传递给 params。为什么?
【发布时间】:2014-12-17 01:06:57
【问题描述】:

出于测试/学习目的,我有一个具有以下属性的测验模型(来自 schema.rb 的 sn-p):

create_table "quizzes", force: true do |t|
    t.integer  "user_id"
    t.string   "answer1"
    t.string   "answer2"
    t.string   "answer3"
    t.string   "answer4"
    t.string   "answer5"
    t.string   "answer6"
    t.string   "answer7"
    t.string   "answer8"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  add_index "quizzes", ["user_id"], name: "index_quizzes_on_user_id"

测验被发送到“新”视图,如下所示:

def new
    @quiz = Quiz.new(user_id: current_user.id)
end

当用户完成测验时,QuizzesController#create 动作被触发:

def create
    @results = Quiz.create(post_params) #from private method
    render :show
end

(无论是呈现“显示”视图还是默认的“创建”视图,它都有效 - 在我测试和学习时,我现在将这两个视图都用作占位符)

post_params 方法如下所示:

def post_params
    params.require(:quiz).permit(:user_id, :answer1, :answer2, :answer3, :answer4, :answer5, :answer6, :answer7, :answer8) #add other attributes here
end

(我也尝试过params.require(:quiz).permit!,但得到了相同的结果)。

问题是,在视图中,它看起来像这样:

<div class="container">
      <!-- Example row of columns -->
      <div class="row">
        <h1>Results</h1>
        <p><%= @results.inspect %></p>
    </div>
</div>

...@results.inspect 行显示所有 answer1、answer2 等属性,但将 user_id 属性显示为 nil - 尽管在 QuizzessController#new 操作中使用 user_id 设置了测验。我做错了什么,导致 user_id 无法正确传递?

【问题讨论】:

    标签: ruby-on-rails ruby controller


    【解决方案1】:

    如果您总是要将user_id 设置为current_user.id,我建议您在create 操作中添加该属性。

    这样您就不必担心用户输入。否则,用户可能会滥用它来更改 id,因此测验会被添加到另一个用户。

    首先,从允许的post_params

    中删除user_id
    def post_params
      params.require(:quiz).permit(:answer1, :answer2, :answer3, :answer4, :answer5, :answer6, :answer7, :answer8)
    end
    

    然后只需将您的创建操作更新为:

    def create
      # as mentioned by tagCindy, this is the way to set up the association
      @quiz = current_user.build_quiz(post_params)
    
      # you should also re-render the new form if there are any errors
      if @quiz.save
        render :show
      else
        render :new
      end
    end
    

    【讨论】:

    • 感谢您的回复!我用过@results = Quiz.create(post_params.merge(:user_id =&gt; current_user.id)),这似乎成功了! (我认为,.create 也保存了对象,所以我不需要大多数其他行)。再次感谢。
    • 不得不否决这个答案,因为它忽略了将用户正确关联到测验的需要。
    • 没关系,为什么current_user.quizzes.create(quiz_params)更好?
    • 我相信save 会调用valid?。所以不需要@quiz.valid? 只需:if @quiz.save
    • @TheChamp 进行关联的方式增加了逻辑上不必要的复杂性。不必要的复杂性会增加发生异常的机会。例如,如果 post_params 格式错误、nil 或不是 copacci strong_params 哈希,则用户将在 .merge 操作上收到错误,而不是预期的验证错误。我经常使用这种方法将额外的数据注入到 params 哈希中,这样本身就没有错。但如果 ActiveRecord 为您提供了简化关联的方法,我建议使用它。
    【解决方案2】:

    首先,查看ActiveRecord associations。您的测验模型和您的用户模型之间应该有关联(多对一或一对一)。如果关联设置正确,并且 current_user 数据保存到会话中,则无需将 user_id 与 POST 一起传递。

    class QuizzesController < ApplicationController
    # grabs the current_user from the session
    before_filter :get_current_user
    
    # ...
    
    def create
        # METHOD 1: initialize and persist, check for errors
        @quiz = current_user.create_quiz(quiz_params)
        if @quiz.errors
            render :new
        else
            redirect_to @quiz # or where ever
    
       # METHOD 2: initialize, check for successful save
       @quiz = current_user.build_quiz(quiz_params)
       if @quiz.save
            render :new
        else
            redirect_to @quiz # or where ever
    end
    
    # ...
    
    private
    
    def get_current_user
        @current_user = User.find(session[:user_id])
    end
    
    def quiz_params
        params.require(:quiz).permit(:answer1, :answer2, :answer3, :answer4, :answer5, :answer6, :answer7, :answer8)
    end
    
    end
    

    如果您不希望将 user_id 存储在会话中,我会设置您的路由,以便将 user_id 传递给路由中的控制器。应不惜一切代价避免将 user_id 作为表单上的隐藏值传递,因为它不安全并且很容易被操纵和/或欺骗。

    附带说明,您应该有一个与 Quiz 具有多对一关系的 Answer 模型。

    【讨论】:

    • 我知道我应该有测验has_many :quiz_answers,但我正在尝试尽可能简单地一次设置一个步骤。在我的测验模型中它是belongs_to :user,在用户中它是has_one :quiz。在什么时候我可以参考 current_user.quiz?是否必须在 current_user 上设置测验模型,或者这是错误的思考方式?当我尝试 current_user.quiz.build(..etc..) 时,它抛出了 current_user.quiz = nil 错误。
    • 自用户 has_one :quiz 以来,我刚刚尝试过 @results = current_user.quiz.create(post_params)(使用原始问题中的 post_params 方法,减去 user_id),但这也会引发“未定义的方法‘create’ nil:NilClass",所以看起来 current_user.quiz 在那一点上是 nil。
    • has_one 没有createbuild 方法;您需要使用create_quizbuild_quiz。请参阅我的更新答案。
    • 嘿,我喜欢这个答案。根据我的回答,尽管关系是has_one,是否可以创建多个测验?
    • @TheChamp 不,如果您尝试将第二个测验保存给用户,您会遇到异常
    【解决方案3】:

    那是因为post_params 旨在处理提供给控制器的参数。即 POST/GET

    user_id(我假设)不在您的表单中,因此不会通过表单提供给控制器。

    我会做的是:

    def create
      @results = Quiz.create(post_params.merge(user_id: current_user.id) #from private method
      render :show
    end
    

    如果您正确设置了关系,那就更好了(如果它是 1>1,则单数化测验):

    def create
      @results = current_user.create_quiz(post_params) #this will fill in the user_id field for you
      render :show
    end
    

    在您的表单中,请勿

    = f.hidden_field :user_id, current_user.id
    

    这样做意味着任何用户都可以为任何其他用户创建测验。

    【讨论】:

    • 在您的第二个解决方案中,您不必保存build 吗?我不认为这是自动完成的
    • 用户has_one :quiz,所以它不喜欢current_user.quizzes.build(post_params)这一行。此外,如果我将其更改为current_user.quiz.build(post_params),我会收到一条错误消息,指出 .quiz 为 nil(我猜是因为尚未在用户上设置)。如果我对发生该错误的原因有误,请告诉我...
    • @moosefetcher 你是对的,它将为零。我确实说过假设您的关系设置正确。
    • 这并不是说用户has_one :quiz 是错误的关联,对吧?只是你认为它可能是别的东西......
    • @moosefetcher 对不起,我错了,has_one 关系没有build 方法。天知道为什么。我已经编辑了我的答案。
    猜你喜欢
    • 2011-03-29
    • 1970-01-01
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-03
    相关资源
    最近更新 更多