Rails游戏项目:如何从Scheduler.rb触发HTML事件更新香蕉状态
嘿,这个场景我之前碰到过——后端异步线程完成状态更新后,要主动通知前端触发对应的交互,对吧?核心问题是后端无法直接触发前端事件,得通过前后端通信的方式来实现,下面给你几个适配Rails项目的可行方案:
方案1:用Action Cable(WebSocket实时推送)
这是最适合实时场景的方案,Rails官方提供的WebSocket框架,能让后端主动给前端发消息。
步骤拆解:
创建更新通知Channel
先生成一个Channel来处理香蕉更新的广播:rails generate channel BananaUpdates然后在
app/channels/banana_updates_channel.rb里写广播逻辑:class BananaUpdatesChannel < ApplicationCable::Channel def subscribed # 可以按花园或者用户订阅,这里假设按花园ID分组 stream_from "banana_updates_#{params[:garden_id]}" end def unsubscribed # 清理逻辑,可选 end # 提供给Scheduler调用的广播方法 def self.broadcast_banana_update(garden_id, banana) broadcast_to "banana_updates_#{garden_id}", { banana_id: banana.id, ripeness: banana.ripeness, action: "update_ripeness" } end end在Scheduler.rb里触发广播
当香蕉成熟度更新完成后,调用上面的广播方法:# Scheduler.rb中更新香蕉成熟度的逻辑里 banana.update(ripeness: new_ripeness, nexttime: next_update_time) # 广播更新事件到对应花园的Channel BananaUpdatesChannel.broadcast_banana_update(banana.garden_id, banana)注意:如果Scheduler是独立线程,要确保线程能正确访问Rails的环境(比如数据库连接),可以在线程初始化时加载Rails环境,或者用
Rails.application.executor.wrap包裹操作。前端订阅Channel并触发HTML事件
在前端的JS文件(比如app/javascript/channels/banana_updates.js)里订阅消息,收到后触发自定义HTML事件:import consumer from "./consumer" consumer.subscriptions.create({ channel: "BananaUpdatesChannel", garden_id: YOUR_GARDEN_ID }, { received(data) { // 触发自定义HTML事件,比如给对应的香蕉元素发事件 const bananaElement = document.getElementById(`banana-${data.banana_id}`); if (bananaElement) { const updateEvent = new CustomEvent("bananaRipenessUpdated", { detail: { ripeness: data.ripeness } }); bananaElement.dispatchEvent(updateEvent); } } }); // 监听自定义事件,执行你需要的操作 document.addEventListener("bananaRipenessUpdated", (e) => { // 比如更新香蕉图标、显示提示等 console.log("香蕉成熟度更新了:", e.detail.ripeness); // 这里写你的HTML事件逻辑,比如切换图标类名 e.target.classList.add(`ripe-${e.detail.ripeness}`); });
方案2:用Turbo Streams(适合Rails 7+ Hotwire项目)
如果你的项目用了Hotwire/Turbo,这个方案更简洁,不需要写太多JS代码。
步骤:
Scheduler里广播Turbo Stream
当香蕉更新完成后,直接广播Turbo Stream片段,既可以更新DOM,也能触发事件:# Scheduler.rb中 banana.update(ripeness: new_ripeness, nexttime: next_update_time) # 广播一个自定义动作,触发前端事件 Turbo::StreamsChannel.broadcast_action_to( "banana_updates_#{banana.garden_id}", action: "dispatch_event", target: "banana-#{banana.id}", event: "bananaRipenessUpdated", data: { ripeness: banana.ripeness }.to_json )前端页面订阅Stream
在你的花园视图页面里添加订阅:<%= turbo_stream_from "banana_updates_#{@garden.id}" %>然后在JS里监听自定义事件即可,和方案1的前端监听逻辑一样。
方案3:轮询(简单但非实时)
如果不想搞WebSocket,也可以用前端定时轮询后端接口,检查香蕉状态,有更新就触发事件。这种方式适合对实时性要求不高的场景:
前端示例:
setInterval(async () => { const response = await fetch(`/gardens/${YOUR_GARDEN_ID}/bananas/status`); const updatedBananas = await response.json(); updatedBananas.forEach(banana => { const bananaElement = document.getElementById(`banana-${banana.id}`); if (bananaElement && bananaElement.dataset.ripeness !== banana.ripeness) { bananaElement.dataset.ripeness = banana.ripeness; // 触发事件 bananaElement.dispatchEvent(new CustomEvent("bananaRipenessUpdated", { detail: banana })); } }); }, 5000); // 每5秒轮询一次
后端要加对应的接口:
# app/controllers/bananas_controller.rb def status @garden = Garden.find(params[:garden_id]) @bananas = @garden.bananas.select(:id, :ripeness) render json: @bananas end
注意事项
- 独立线程的Scheduler要确保线程安全,比如数据库连接的释放,避免连接泄漏。可以用
ActiveRecord::Base.connection_pool.with_connection包裹数据库操作。 - 如果用Action Cable,要确保Redis(或其他适配器)配置正确,因为Action Cable默认用Redis做广播。
内容的提问来源于stack exchange,提问作者user9675688

