Rails开发MCQ答题应用 存储用户选项比对答案展示结果方案
Rails多选题答题&判分功能实现方案
你现有代码存在3个核心问题:Result表字段命名不符合Rails关联约定、表单嵌套导致提交异常、控制器未实现接收参数+判分+存库逻辑,按以下步骤调整即可完成需求:
第一步:修正基础配置问题
首先处理Result表的字段不规范问题:Rails中belongs_to关联默认会找[关联模型名]_id格式的外键,你当前写的q_id、u_id会直接导致关联报错。
- 如果你还没执行过CreateResults的迁移,可以直接修改原迁移文件:
class CreateResults < ActiveRecord::Migration[6.1] def change create_table :results do |t| t.text :user_choice t.references :question, null: false, foreign_key: true t.references :user, null: false, foreign_key: true t.boolean :is_correct, default: false # 新增字段存储判题结果,避免后续重复比对 t.timestamps end end end
- 如果已经执行过原迁移,生成新的修正迁移即可:
rails generate migration FixResultForeignKeys
编辑迁移文件内容:
class FixResultForeignKeys < ActiveRecord::Migration[6.1] def change rename_column :results, :q_id, :question_id rename_column :results, :u_id, :user_id add_foreign_key :results, :questions add_foreign_key :results, :users add_column :results, :is_correct, :boolean, default: false end end
执行迁移完成表结构更新:
rails db:migrate
补全模型关联:
- 在
app/models/question.rb中添加反向关联、封装选项获取逻辑:
class Question < ApplicationRecord has_many :results # 封装选项获取逻辑,避免视图重复拼数组 def options [option1, option2, option3, option4] end # 可选:存题时自动清洗选项、答案的首尾空格,避免判题误差 before_save :strip_text_fields private def strip_text_fields self.question = question.strip self.option1 = option1.strip self.option2 = option2.strip self.option3 = option3.strip self.option4 = option4.strip self.answer = answer.strip end end
- 你的Result模型代码不需要大改,保留原有验证和关联即可。
配置路由,修改config/routes.rb:
Rails.application.routes.draw do resources :questions, only: [:index, :new, :create] resources :results, only: [:create] get '/results', to: 'results#index', as: :user_results # 结果展示页路由 end
第二步:修正视图表单错误
你当前视图的核心问题是form_with内部嵌套了button_to,会生成两个重叠的form标签导致提交异常,同时单选框参数命名不规范,后端无法批量接收多题答案。把app/views/questions/index.html.erb替换为以下内容:
<div class="card o-hidden border-0 shadow-lg my-5"> <div class="card-body"> <div class="row py-5"> <div class="p-5 w-100"> <div class="text-center"> <h1 class="h4 text-gray-900 mb-4">Multiple-Choice Questions</h1> </div> <%# 直接用form_with提交到results创建路径,不嵌套button_to %> <%= form_with url: results_path, method: :post do |form| %> <% @questions.each do |question| %> <div id="<%= dom_id(question) %>" class="mb-4"> <p class="font-weight-bold"><%= question.question %></p> <% question.options.each_with_index do |option, idx| %> <div class="form-check ml-3"> <%# 用answers[question_id]的参数结构,后端可直接拿到{题目id:用户选择}的哈希 %> <%= radio_button_tag "answers[#{question.id}]", option, false, class: "form-check-input", id: "q_#{question.id}_opt_#{idx}" %> <%= label_tag "q_#{question.id}_opt_#{idx}", option, class: "form-check-label" %> </div> <% end %> </div> <% end %> <div class="sub mt-4"> <%= form.submit "提交答案", class: "btn btn-primary" %> </div> <% end %> <div class="para1 mt-3"> <%= link_to '新建题目', new_question_path, class:"btn btn-primary btn-user" %> </div> </div> </div> </div> </div>
同时补全Questions控制器的index动作,确保能拿到所有题目:
class QuestionsController < ApplicationController def index @questions = Question.all end # 你原有的new、create动作保持不变即可 end
第三步:实现控制器判分&存库逻辑
生成Results控制器:
rails generate controller Results
编辑app/controllers/results_controller.rb,实现提交、判分、结果展示逻辑:
class ResultsController < ApplicationController # 如果你用devise做用户认证保留这行,自己实现的登录逻辑替换为对应鉴权方法 before_action :authenticate_user! def create submitted_answers = params[:answers] @results = [] @score = 0 @total = submitted_answers.count # 用事务保证所有答题记录要么全部存成功,要么全部回滚 ActiveRecord::Base.transaction do submitted_answers.each do |question_id, user_choice| question = Question.find(question_id) is_correct = user_choice.strip == question.answer @score += 1 if is_correct result = Result.create!( user: current_user, # 没做登录的话临时替换为User.first测试功能 question: question, user_choice: user_choice.strip, is_correct: is_correct ) @results << result end end flash[:notice] = "答题完成!你的得分是#{@score}/#{@total}" redirect_to user_results_path, results: @results rescue ActiveRecord::RecordNotFound, ActiveRecord::RecordInvalid => e flash[:alert] = "提交失败:#{e.message}" redirect_to questions_path end def index @results = flash[:results] || current_user.results.includes(:question).order(created_at: :desc).limit(10) @score = @results.count { |r| r.is_correct } @total = @results.count end end
第四步:编写结果展示页
新建app/views/results/index.html.erb,用于展示答题对错反馈:
<div class="card o-hidden border-0 shadow-lg my-5"> <div class="card-body p-5"> <div class="text-center mb-4"> <h1 class="h4 text-gray-900 mb-2">答题结果</h1> <p class="lead"><%= flash[:notice] %></p> </div> <% @results.each do |result| %> <div class="mb-4 p-3 border rounded <%= result.is_correct ? 'border-success bg-light' : 'border-danger bg-light' %>"> <p class="font-weight-bold"><%= result.question.question %></p> <p>你的答案:<span class="<%= result.is_correct ? 'text-success' : 'text-danger' %>"><%= result.user_choice %></span></p> <% unless result.is_correct %> <p class="text-success">正确答案:<%= result.question.answer %></p> <% end %> <p class="mb-0">判定:<strong><%= result.is_correct ? "正确" : "错误" %></strong></p> </div> <% end %> <div class="mt-4"> <%= link_to "返回答题页", questions_path, class: "btn btn-primary" %> </div> </div> </div>
可选优化项
- 如果暂时没做用户登录模块,可以先把控制器里的
current_user替换为固定测试用户,等登录功能做完再替换回来 - 可以加参数校验,防止用户漏选题目直接提交
- 可以新增得分统计表,记录用户每次答题的总分、答题时间,做历史答题记录功能
内容的提问来源于stack exchange,提问作者Abu Bakar
相关产品推荐
相关产品推荐

