基于Rails构建无刷新链式问答应用的技术实现咨询
实现Rails单页链式问答功能方案
嘿,我来帮你搞定这个链式问答的功能!首先你的现有数据模型还需要补充一些核心字段来支撑“答案决定下一个问题”的逻辑,然后结合Rails生态里的Turbo和Stimulus,就能轻松实现单页无刷新的交互效果。
1. 完善数据模型
首先得给GameBlock补充关键字段,用来存储问题内容、答案选项以及关联的下一个问题:
生成迁移文件
rails generate migration AddQuestionFieldsToGameBlocks question:text options:json next_block_id:integer user_answer:string rails db:migrate
更新模型代码
修正Game模型的关联(注意复数形式),同时给GameBlock添加上必要的关联和验证:
# app/models/game.rb class Game < ApplicationRecord belongs_to :user has_many :game_blocks, dependent: :destroy accepts_nested_attributes_for :game_blocks, allow_destroy: true # 快速获取第一个问题 def first_block game_blocks.order(:created_at).first end end
# app/models/game_block.rb class GameBlock < ApplicationRecord belongs_to :game belongs_to :next_block, class_name: "GameBlock", optional: true # 验证核心字段必填 validates :question, presence: true validates :options, presence: true end
2. 后端API端点
我们需要两个核心接口:一个用来加载游戏的初始问题,另一个用来接收用户答案并返回下一个问题:
# app/controllers/games_controller.rb class GamesController < ApplicationController before_action :set_game, only: [:show, :update_answer] # 加载游戏页面,渲染第一个问题 def show @current_block = @game.first_block end # 处理用户提交的答案,返回下一个问题 def update_answer current_block = @game.game_blocks.find(params[:block_id]) current_block.update(user_answer: params[:answer]) @next_block = current_block.next_block respond_to do |format| format.turbo_stream do if @next_block render turbo_stream: turbo_stream.replace("question-container", partial: "game_blocks/block", locals: { block: @next_block }) else render turbo_stream: turbo_stream.replace("question-container", partial: "game_blocks/end") end end end end private def set_game @game = Game.find(params[:id]) end end
别忘了在config/routes.rb里添加路由:
resources :games do member do post :update_answer end end
3. 前端无刷新交互(Stimulus + Turbo)
用Stimulus来监听用户的答案选择事件,结合Turbo Stream实现无刷新替换问题:
创建Stimulus控制器
// app/javascript/controllers/question_controller.js import { Controller } from "@hotwired/stimulus" export default class extends Controller { static values = { gameId: Number, blockId: Number } submitAnswer(event) { event.preventDefault() const answer = event.target.value fetch(`/games/${this.gameIdValue}/update_answer`, { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": document.querySelector('meta[name="csrf-token"]').content }, body: JSON.stringify({ block_id: this.blockIdValue, answer: answer }) }) } }
4. 视图层实现
游戏主页面(app/views/games/show.html.erb)
<div> <%= turbo_frame_tag "question-container" do %> <%= render partial: "game_blocks/block", locals: { block: @current_block, game_id: @game.id } %> <% end %> </div>
问题局部视图(app/views/game_blocks/_block.html.erb)
<% if block %> <div data-controller="question" data-question-game-id-value="<%= game_id %>" data-question-block-id-value="<%= block.id %>" > <h3><%= block.question %></h3> <% block.options.each do |option| %> <button type="button" data-action="question#submitAnswer" value="<%= option %>" class="px-4 py-2 m-2 bg-blue-500 text-white rounded" > <%= option %> </button> <% end %> </div> <% end %>
问答结束局部视图(app/views/game_blocks/_end.html.erb)
<div> <h3 class="text-xl font-bold">问答结束!感谢参与</h3> <p>你的所有答案已保存,可在个人中心查看记录</p> </div>
核心逻辑说明
- 打开游戏页面时,自动加载当前游戏的第一个
GameBlock - 用户点击答案按钮后,Stimulus控制器发送POST请求到后端接口
- 后端更新用户答案后,通过Turbo Stream无刷新替换页面上的问题区域
- 当没有下一个问题时,自动切换到问答结束的提示页面
这样就完全实现了单页无刷新、答案驱动下一个问题的链式问答功能啦!
内容的提问来源于stack exchange,提问作者Val-Lee
相关产品推荐
相关产品推荐

