Turbo Frame与Turbo Stream在后台任务更新记录时的异常问题
Turbo Stream更新消息未同步问题排查与解决方案
环境配置
- Ruby 3.2.0
- Rails 8.0.1
代码实现
路由配置(config/routes.rb)
Rails.application.routes.draw do mount MissionControl::Jobs::Engine, at: "/jobs" get "up" => "rails/health#show", as: :rails_health_check resources :messages root "messages#index" end
消息控制器(app/controllers/messages_controller.rb)
class MessagesController < ApplicationController def index @messages = Message.all.order(created_at: :asc) end def create @user_message = Message.create( content: params[:content], role: "user" ) @assistant_message = Message.create( content: "Searching the answer for you...", role: "assistant" ) ProcessChatMessageJob.perform_later(@user_message, @assistant_message) respond_to(&:turbo_stream) end end
消息模型(app/models/message.rb)
class Message < ApplicationRecord validates :content, presence: true, length: { minimum: 3, maximum: 1000 } after_create_commit do broadcast_append_to "messages", target: "messages", partial: "messages/message", locals: { message: self } end after_update_commit do broadcast_update_to "messages", target: "message_#{id}", partial: "messages/message", locals: { message: self } end end
消息列表视图(app/views/messages/index.html.erb)
<div class="max-w-2xl mx-auto p-4 space-y-6"> <div id="messages" class="border p-4 rounded shadow"> <%= turbo_stream_from "messages" %> <% @messages.each do |message| %> <%= render partial: "messages/message", locals: { message: message } %> <% end %> </div> <div id="message_form"> <%= render "messages/form" %> </div> </div>
创建消息Turbo Stream视图(app/views/messages/create.turbo_stream.erb)
<%= turbo_stream.replace "message_form" do %> <%= render "messages/form" %> <% end %>
消息表单视图(app/views/messages/_form.html.erb)
<%= form_with url: messages_path, method: :post, data: { turbo_frame: "messages" }, class: "flex flex-col gap-2" do |f| %> <%= f.text_area :content, placeholder: "Enter a new message...", class: "p-2 border rounded", rows: 3 %> <%= f.submit "Send", class: "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" %> <% end %>
消息部分视图(app/views/messages/_message.html.erb)
<%= turbo_frame_tag dom_id(message) do %> <div class="p-4"> <div class="flex my-1 <%= message.role == 'user' ? 'justify-end' : 'justify-start' %>"> <div class="<%= message.role == 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200 text-gray-800' %> max-w-xs md:max-w-md p-4 rounded-lg shadow-lg"> <%= markdown(message.content) %> </div> </div> </div> <% end %>
处理消息任务(app/jobs/process_chat_message_job.rb)
class ProcessChatMessageJob < ApplicationJob queue_as :default def perform(user_message, assistant_message) user_message_content = user_message.content sql_query = Ai::SqlQueryGenerator.new(user_message_content).call if sql_query.include?("Sorry") assistant_message.update!(content: sql_query) return end puts "Executing SQL query: #{sql_query}" result = execute_sql_query(sql_query) puts "Result of sql query: #{result}" formatted_answer = Ai::ResultFormatter.new(result).call puts "Formatted answer: #{formatted_answer}" assistant_message.update!(content: formatted_answer) end private def execute_sql_query(sql_query) ActiveRecord::Base.connection.execute(sql_query) rescue => e puts "Error executing SQL query: #{e.message}" nil end end
日志摘要
# Job Enqueue & Execution [ActiveJob] Enqueued ProcessChatMessageJob (...) with arguments: Message/406, Message/407 ... [ActiveJob] Performing ProcessChatMessageJob (...) Executing SQL query: SELECT ... FROM tournament_years ty ... LIMIT 20; ... Message Update (...) UPDATE "messages" SET "content" = '# Grand Slam Wins by Legendary Tennis Players ...' WHERE "messages"."id" = 407 ... # Broadcasting Updated Message [ActiveJob] Enqueued Turbo::Streams::ActionBroadcastJob ... with arguments: "messages", { action: :replace, target: "message_407", partial: "messages/message", locals: { message: Message/407 } } ... [ActiveJob] Performed Turbo::Streams::ActionBroadcastJob (Job ID: ...) from SolidQueue(default) in ...ms
问题详情
当前行为
- 表单提交时:MessagesController#create创建用户消息与带"Searching the answer for you..."占位文本的助手消息,两者通过after_create_commit广播追加到"messages"流;表单容器会被Turbo Stream替换。
- 后台任务处理后:ProcessChatMessageJob更新助手消息内容,触发after_update_commit广播更新对应Turbo Frame,日志显示广播已执行。
问题
后台任务完成后,数据库中助手消息已更新,但页面上的消息未同步刷新。
排查思路与解决方案
1. 修正表单的Turbo Frame配置错误
表单中data: { turbo_frame: "messages" }指定的messages是普通div元素,并非Turbo Frame标签,这会导致Turbo处理表单提交时出现异常,干扰后续WebSocket广播的接收。
解决方案:移除表单的turbo_frame数据属性:
<%= form_with url: messages_path, method: :post, class: "flex flex-col gap-2" do |f| %> <%= f.text_area :content, placeholder: "Enter a new message...", class: "p-2 border rounded", rows: 3 %> <%= f.submit "Send", class: "px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" %> <% end %>
2. 验证WebSocket连接状态
- 打开浏览器开发者工具,切换到
Network标签页,检查是否存在cable开头的WebSocket连接,确认连接状态为Open。 - 若连接失败,检查
config/cable.yml配置,确保开发环境的adapter为async或正确的Redis配置,同时确认config/environments/development.rb中启用了Action Cable:config.action_cable.mount_path = '/cable' config.action_cable.url = 'ws://localhost:3000/cable' config.action_cable.allowed_request_origins = [/http:\/\/*/, /https:\/\/*/]
3. 确认广播目标ID匹配
- 查看页面渲染后的HTML,确认助手消息的Turbo Frame ID为
message_xxx(如message_407),与模型中broadcast_update_to指定的target: "message_#{id}"完全一致。 - 可在模型的
after_update_commit中添加日志,确认广播的目标ID和内容:after_update_commit do Rails.logger.info "Broadcasting update to message_#{id}" broadcast_update_to "messages", target: "message_#{id}", partial: "messages/message", locals: { message: self } end
4. 检查前端JavaScript错误
- 打开浏览器开发者工具的
Console标签页,查看是否有Turbo相关的JavaScript错误,若存在错误需先修复,确保Turbo脚本正常加载并运行。
5. 验证广播内容正确性
- 可手动触发广播测试,在rails控制台执行:
观察页面是否更新,若仍未更新,检查partial渲染的内容是否正确。message = Message.find(407) message.broadcast_update_to "messages", target: "message_#{message.id}", partial: "messages/message", locals: { message: message }
内容的提问来源于stack exchange,提问作者elviajero34
相关产品推荐
相关产品推荐

