You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails 6嵌套表单开发测验应用逐题提交实现方案咨询

你选的嵌套表单+最终统一提交的方案完全可行,比逐题调用接口存临时答案的实现成本更低、逻辑更顺,适合这个答题场景。你现在跑不通的核心原因是几个代码逻辑缺口,按下面步骤改就能实现效果。

现有代码的核心问题
  • new动作中仅构建了1个quiz_answer实例,10道题需要对应10个独立的answer实例才能存储所有答案,当前代码的fields_for只会渲染1组答案输入框
  • 局部视图遍历对象错误:@quiz是初始化的空Quiz实例,不是题目集合,和嵌套表单的结构完全不匹配
  • 缺少单题显示、切换的前端交互逻辑,默认会把所有题目一次性渲染出来
  • 缺少嵌套属性对应的强参数配置,提交时答案参数会被Rails默认拦截
具体修改步骤

1. 调整控制器逻辑

首先修改QuizzesController,给每道激活的题目预构建对应答案实例,补上强参数配置:

class QuizzesController < ApplicationController
  def new
    @quiz = Quiz.new
    # 查询所有启用的题目,可根据需要调整排序规则
    @active_questions = QuizQuestion.where(active: true).order(:id)
    # 为每道题预构建空的关联答案,绑定对应题目ID
    @active_questions.each do |question|
      @quiz.quiz_answers.build(quiz_question_id: question.id)
    end
  end

  def create
    @quiz = Quiz.new(quiz_params)
    # 绑定当前答题用户ID,若使用Devise认证可直接取current_user
    @quiz.user_id = current_user.id

    respond_to do |format|
      if @quiz.save
        # 可在此处或模型回调中添加判分逻辑,对照选项的correct字段计算得分
        format.html { redirect_to quiz_url(@quiz), notice: "测验提交成功" }
        format.json { render :show, status: :created, location: @quiz }
      else
        # 渲染错误页时重新加载题目列表
        @active_questions = QuizQuestion.where(active: true).order(:id)
        format.html { render :new, status: :unprocessable_entity }
        format.json { render json: @quiz.errors, status: :unprocessable_entity }
      end
    end
  end

  private
  def quiz_params
    params.require(:quiz).permit(
      :user_id,
      quiz_answers_attributes: [:id, :quiz_question_id, :quiz_option_id, :answer, :_destroy]
    )
  end
end

2. 重构表单视图

原来的局部视图可以直接废弃,把答题逻辑整合到主表单中,默认仅显示第一题,其余题目隐藏:

<%# app/views/quizzes/new.html.erb %>
<%= form_with(model: @quiz) do |form| %>
  <div class="quiz-container">
    <% @active_questions.each_with_index do |question, index| %>
      <%# 按索引匹配预构建的answer表单构造器 %>
      <% answer_form = form.fields_for(:quiz_answers)[index] %>
      <div class="question-item" data-index="<%= index %>" style="<%= index == 0 ? '' : 'display: none;' %>">
        <h3><%= question.question %></h3>
        <%# 隐式传入题目ID,绑定答案和题目的关联关系 %>
        <%= answer_form.hidden_field :quiz_question_id, value: question.id %>
        
        <% question.quiz_options.each do |option| %>
          <div class="form-check mb-2">
            <%= answer_form.radio_button :quiz_option_id, option.id, class: 'form-check-input', required: true %>
            <%= answer_form.label :quiz_option_id, option.answer, value: option.id, class: 'form-check-label' %>
          </div>
        <% end %>

        <div class="mt-4">
          <% if index == @active_questions.size - 1 %>
            <%# 最后一题显示提交按钮 %>
            <%= form.submit "提交测验", class: 'btn btn-primary' %>
          <% else %>
            <%# 非最后一题显示下一题按钮 %>
            <button type="button" class="btn btn-primary next-question" data-target="<%= index + 1 %>">下一题</button>
          <% end %>
          <%# 可选:上一题返回按钮 %>
          <% if index > 0 %>
            <button type="button" class="btn btn-secondary prev-question" data-target="<%= index - 1 %>">上一题</button>
          <% end %>
        </div>
      </div>
    <% end %>
  </div>
<% end %>

3. 添加前端切换逻辑

用原生JS实现题目的显示/隐藏切换,加选项非空校验,不需要引入额外前端框架。如果是Rails 6默认的Webpacker环境,可以把代码写到app/javascript/packs/quizzes.js中,在对应页面引入;也可以直接写在页面底部的<script>标签里:

document.addEventListener('DOMContentLoaded', () => {
  // 下一题逻辑
  document.querySelectorAll('.next-question').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const currentBlock = e.target.closest('.question-item')
      const targetIndex = e.target.dataset.target
      // 校验当前题是否已选答案
      const selected = currentBlock.querySelector('input[type="radio"]:checked')
      if (!selected) {
        alert('请先选择答案再进入下一题')
        return
      }
      // 切换题目显示
      currentBlock.style.display = 'none'
      document.querySelector(`.question-item[data-index="${targetIndex}"]`).style.display = 'block'
      // 可选:滚动到页面顶部
      window.scrollTo(0, 0)
    })
  })

  // 上一题逻辑(可选)
  document.querySelectorAll('.prev-question').forEach(btn => {
    btn.addEventListener('click', (e) => {
      const currentBlock = e.target.closest('.question-item')
      const targetIndex = e.target.dataset.target
      currentBlock.style.display = 'none'
      document.querySelector(`.question-item[data-index="${targetIndex}"]`).style.display = 'block'
      window.scrollTo(0, 0)
    })
  })
})
可选优化项
  • 可添加答题进度条,展示当前答题进度,提升用户体验
  • 判分逻辑建议放到Quiz模型的回调中实现,自动对照选项的correct字段标记答案对错、计算得分,避免控制器逻辑过重
  • 可将用户已选答案临时存在localStorage中,防止页面刷新导致答案丢失
  • 提交前可增加答案确认页,展示用户所有选择供核对后再提交

内容的提问来源于stack exchange,提问作者yellowskull

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 07:12:52