在 rails 中创建嵌套对象的复选框

Check box that creates nested objects in rails

我正在构建一个创建考试的应用程序。对于用户选择考试答案的部分,我想使用复选框(或单选按钮)让他们选择答案。

我希望所有用户选择的答案都是 table 本身,称为 "responses"。我不知道如何使用单选按钮创建记录。

所有响应记录需要做的就是获取考试、用户和分数的 ID。 Score 是一个 table ,用于跟踪用户的分数和正确答案的数量。 这是我的考试模型(rails 不让我使用 "exam" 这个词)。我为嵌套属性设置了它。

class Examination < ApplicationRecord
  belongs_to :user
  has_many :questions, dependent: :destroy
  has_many :scores
  has_many :responses
  has_secure_password

  accepts_nested_attributes_for :responses, allow_destroy: true
end

响应模型非常基础:

class Response < ApplicationRecord
  belongs_to :user
  belongs_to :score
  belongs_to :examination
end

这是 "take an exam" 页面: <%= link_to "Back to all exams", examinations_path %>

<h2><%= @exam.name %></h2>
<h3><%= @exam.intro %></h3>

<%= form_for @exam  do |f| %>
  <%= f.hidden_field :name, value: @exam.name  %>
  <%= fields_for :responses do |res_f| %>
    <% @exam.questions.each_with_index do |question, i| %>
      <% index = i + 1 %>
      <h2>Question #<%=index%></h2><span style="font-size: 24px; font-weight: normal">(<%= question.points %> Points)</span>
      <hr>
      <h3><%= question.body %></h3>
      <% question.answers.each do |ans| %>
        <table>
          <tr>
            <td><%= res_f.check_box :answer_id , ans.id, :examination_id , @exam.id, :user_id  %></td>
            <td><%= ans.body %></td>
          </tr>
        </table>
      <% end %>
    <% end %>
  <% end %>

  <%= f.submit 'Submit' %>
<% end %>

此代码不 运行 因为 Rails 期望响应记录存在才能使用表单。它抛出这个错误:

undefined method `merge' for 484:Integer

如果我将复选框代码调整为:

<%= res_f.check_box :answer_id  %>

代码将 运行 并在提交时给我以下参数:

Started PATCH "/examinations/34" for 127.0.0.1 at 2018-02-24 16:22:41 -0800
Processing by ExaminationsController#update as HTML
      Parameters: {"utf8"=>"✓", "authenticity_token"=>"y4vcPByUKnDdM6NsWDhwxh8MxJLZU4TQo+/fUrmKYEfb3qLn5FVieJAYirNRaSl0w5hJax20w5Ycs/wz1bMEKw==", "examination"=>{"name"=>"Samuel Smith’s Oatmeal Stout"}, "responses"=>{"answer_id"=>"1"}, "commit"=>"Submit", "id"=>"34"}

我知道这不对,但我希望它至少能创造一个记录。所有复选框都必须创建响应记录。它应该能够获取 answer_id、exam_id 和 user_id。就是这样。

有人知道怎么做吗?

编辑以回应 Pablo 7: 这是其他模型(它们现在非常基本)

class Score < ApplicationRecord
  belongs_to :user
  belongs_to :examination
  has_many :responses, dependent: :destroy
end

class User < ApplicationRecord
  has_many :examinations, dependent: :destroy
  has_many :scores, dependent: :destroy
  has_many :responses, dependent: :destroy

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
end

class Question < ApplicationRecord
    belongs_to :examination
    has_many :answers, dependent: :destroy

    accepts_nested_attributes_for :answers, allow_destroy: true

    validates_presence_of :body
    validates_presence_of :question_type
end

@exam和Examination是一样的。 Examination 控制器中有一个 "take" 操作允许用户参加考试:

def take
    @exam = Examination.find(params[:id])
    @score = @exam.scores.build
    @score.user_id = current_user.id
    @score.save
end

所以考试属于创建它的用户。同一个用户或不同的用户可以使用 take 操作参加考试。然后他们就会有一个属于他们的分数。

我认为你必须对你的模型做一些修改:

  • 一个响应应该属于一个问题(这是用户正在响应的问题)。

  • 一个响应应该属于一个答案(它是问题的正确答案;用户检查的那个)。如果你想允许多个正确答案,这个应该改变。

  • 回复不应属于检查,也不应属于用户。事实上,一个 Response 属于一个 Score 就足够了,因为 Score 已经属于一个 Examination 和一个 User。

  • 一个考试不应该有很多答案。事实上,一次考试有很多分数,分数有很多答案。如果需要,可以使用 has_many :responses, through: :scores

  • 一个用户不应该有很多回复。他们有很多分数,分数有很多回应。如果需要,可以使用 has_many :responses, through: :scores

创建新分数(in take)时,您应该为考试中的每个问题创建空答案:

def take
  @exam = Examination.find(params[:id])
  @score = @exam.scores.build(user_id: current_user.id)
  @exam.questions.each { |question| @score.responses.build(question_id: question.id) }

  #I don't think you should save here. 
  #This method is like the new method
  #You should save when the score is submitted
  #@score.save 
end

在您的表单中: 我会将表格更改为分数模型(不是考试)。如果您使用嵌套路由,则可以是 [@exam, @score]

这可能有很多错误,因为我现在无法测试它。我希望思路清晰:

<%= form_for @score do |f| %>
  <%= f.hidden_field :name, value: @score.examination.name  %>
  <% @score.responses.each_with_index do |response, i| %>
    <%= f.fields_for response do |res_f| %>
      <% index = i + 1 %>
      <h2>Question #<%= index %></h2>
      <span style="font-size: 24px; font-weight: normal">
        (<%= response.question.points %> Points)
      </span>
      <hr>
      <h3><%= response.question.body %></h3>
      <%= res_f.collection_radio_buttons :answer_id, response.question.answers, :id, :body %>
    <% end %>
  <% end %>

  <%= f.submit 'Submit' %>
<% end %>

提交应该调用 Score 模型中的方法来创建 Score (ScoresController.create)

谢谢 Pablo,我终于让它工作了。你的代码不太管用,但它让我走上了正确的道路。我按照你的建议改变了模型关联。这确实更有意义。

这是我的模型:

class Answer < ApplicationRecord
    belongs_to :question
    has_many :responses, dependent: :destroy
end

class Examination < ApplicationRecord
  belongs_to :user
  has_many :questions, dependent: :destroy
  has_many :answers, :through => :questions
  has_many :scores
  has_secure_password
end

class Question < ApplicationRecord
    belongs_to :examination
    has_many :answers, dependent: :destroy
    has_many :responses

    accepts_nested_attributes_for :answers, allow_destroy: true, :reject_if => :all_blank

    validates_presence_of :body
    validates_presence_of :question_type
end

class Response < ApplicationRecord
  belongs_to :score
  belongs_to :answer
  belongs_to :question
end

class Score < ApplicationRecord
  belongs_to :user
  belongs_to :examination
  has_many :responses, dependent: :destroy

  accepts_nested_attributes_for :responses, allow_destroy: true, reject_if: :no_answer_id? 

  private

 def no_answer_id?(att)
  att['answer_id'].blank?
 end
end

我不得不将该特殊方法添加到评分模型中以解决未经检查的响应。否则,它会抛出错误。

我将 "take a test" 逻辑和视图移至分数控制器。使用您的代码,我得到了一个双循环(多次列出的问题)。我了解到您实际上可以使用 "res_f.object" 通过 "form_for" 表单循环访问响应。这很酷。

我还必须在单选按钮集合表单上添加一个隐藏字段以获取问题 ID。

这里是:

<%= link_to "Back to all exams", examinations_path %><br/>

    <h2><%= @exam.name %></h2>
    <h3><%= @exam.intro %></h3>

<%= form_for [@exam, @score] do |f| %>
  <%= f.hidden_field :user_id, value: current_user.id  %>

    <%= f.fields_for :responses do |res_f| %>

      <h2>Question # <%= res_f.object.question.position %></h2>

      <span style="font-size: 24px; font-weight: normal">
        (<%= res_f.object.question.points %> Points)
      </span>
      <hr> 
      <h3><%= res_f.object.question.body %></h3>
      <p><%= res_f.collection_radio_buttons :answer_id, res_f.object.question.answers, :id, :body do |b| %></p>
        <div>
          <%= b.radio_button %>
          <%= b.label %>
          <%= res_f.hidden_field :question_id, value: res_f.object.question.id %>

        </div>
      <% end %>
    <% end %>


  <%= f.submit 'Submit' %>
<% end %>

分数控制器:

class ScoresController < ApplicationController
    def new
        @exam = Examination.find(params[:examination_id])
        @score = @exam.scores.build(user_id: current_user.id)
        @exam.questions.each do |question|
            res = @score.responses.build(question_id: question.id)
            logger.info question.id
            logger.info res
        end
    end

  def create
    @exam = Examination.find(params[:examination_id])

    @score = @exam.scores.build(score_params)
    if @score.save
        redirect_to examination_path(@exam)
    else
      logger.info @score.errors.full_messages
      redirect_to root_path
    end
  end

   protected

      def score_params
        params.require(:score).permit(:examination_id, :user_id,
          responses_attributes: [:id, :answer_id, :question_id, :selected])
      end
end

如果只有一个正确答案,这一切都很好。稍后我将不得不修改它以解释多个答案。至少它有效!我会把功劳归于巴勃罗。

干杯