【问题标题】:Check box that creates nested objects in rails在导轨中创建嵌套对象的复选框
【发布时间】:2018-02-25 00:33:30
【问题描述】:

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

我希望所有用户选择的答案都是一个名为“响应”的表格。我不知道如何使用单选按钮来创建记录。

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

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

这是“参加考试”页面:

<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。就是这样。

有人知道怎么做吗?

编辑以回应巴勃罗 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 和 Exam 是一样的。考试控制器中有一个“采取”操作,允许用户参加考试:

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

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

【问题讨论】:

  • 你能添加你的问题、分数和用户模型吗?考试和考试是一样的吗?如果是,是否真的和检查属于_一个用户? (没有其他用户参加相同的考试)?
  • 嗨,Pablo,我在问题中添加了更多细节。希望这会有所帮助。真正的问题是如何使用复选框来创建“响应”记录。

标签: ruby-on-rails checkbox nested-forms nested-attributes


【解决方案1】:

我认为您必须对模型进行一些更改:

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

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

  • 响应不应属于考试,也不应属于用户。事实上,响应属于分数,这就足够了,因为分数已经属于考试和用户。

  • 考试不应该有很多响应。事实上,考试有很多分数,分数有很多反应。如果你愿意,你可以使用has_many :responses, through: :scores

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

当你创建一个新的分数(intake)时,你应该为考试中的每个问题创建空答案:

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)

【讨论】:

    【解决方案2】:

    感谢 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
    

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

    我将“参加考试”逻辑和视图移至分数控制器。使用您的代码,我得到了一个双循环(多次列出的问题)。我了解到您实际上可以使用“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 %>
    

    还有 Scores 控制器:

    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
    

    如果只有一个正确答案,这一切都很好。稍后我将不得不对其进行修改以解决多个答案。至少它有效!我会给你 Pablo 的功劳。

    干杯

    【讨论】:

    • 是的。对这些任务使用 ScoresController 中的 new 和 create 方法更有意义。要接受许多答案,您需要一个新模型ResponseAnswer,即belongs_to :responsebelongs_to :answer。对于每个响应,您应该将belongs_to :answer 更改为has_many :response_answershas_many :answers through :response_answers。在表格中,您应该将collection_radio_buttons 更改为collection_check_boxes 并使用answer_ids(复数)。最后,在 score_params 中更改为answer_ids: []。我认为这应该足够了。
    猜你喜欢
    • 1970-01-01
    • 2019-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多