Rails应用中数据变更时如何无需刷新自动更新用户视图?
问题说明
你当前基于Rails开发的类Twitter应用已完成基础功能并部署到Heroku,核心待解决问题为:多用户并发访问时,单用户发布新帖后,其他在线用户无法实时看到内容,必须手动刷新页面。以下是可直接落地的实现方案:
推荐方案:Rails 内置 Action Cable 实现实时推送
这是Rails 5版本后官方原生支持的WebSocket方案,和框架适配成本最低,Heroku平台原生支持该方案的运行要求,是这类场景的首选。
实现步骤如下:
- 生成帖子专属频道
在项目根目录执行命令生成频道文件:rails generate channel Posts - 配置服务端订阅规则
修改生成的app/channels/posts_channel.rb文件,让所有访问帖子列表页的用户都能订阅公共帖子流:
class PostsChannel < ApplicationCable::Channel def subscribed stream_from "public_posts_stream" end def unsubscribed # 断连后无需额外清理逻辑 end end
- 配置新帖自动广播
在Post模型中加创建后的回调,新帖存库成功后自动把渲染好的帖子内容推送到频道,记得先把单条帖子的渲染逻辑抽成partial模板app/views/posts/_post.html.erb,避免重复写视图代码:
# app/models/post.rb class Post < ApplicationRecord # 保留原有的模型关联、校验逻辑 after_create_commit :broadcast_new_post private def broadcast_new_post rendered_post = ApplicationController.renderer.render( partial: "posts/post", locals: { post: self } ) ActionCable.server.broadcast("public_posts_stream", { post_id: id, html: rendered_post }) end end
- 配置前端接收逻辑
修改前端频道文件(Rails 7默认路径为app/javascript/channels/posts_channel.js,老版本对应coffee脚本文件),收到推送的新帖内容后直接插入到帖子列表顶部,同时加去重判断避免重复渲染:
import consumer from "./consumer" consumer.subscriptions.create("PostsChannel", { received(payload) { const postList = document.querySelector(".posts-list") // 页面不存在帖子列表、或者已经渲染过这条帖子就跳过 if (!postList || document.getElementById(`post-${payload.post_id}`)) return // 新帖插到列表最顶部 postList.insertAdjacentHTML("afterbegin", payload.html) } })
- Heroku部署配置
生产环境下Action Cable需要Redis作为消息中转,直接在Heroku后台添加免费版Redis插件即可,同时修改config/cable.yml的生产环境配置:
production: adapter: redis url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> channel_prefix: members_only_prod
另外记得在config/environments/production.rb里配置Action Cable的允许请求源,补上部署域名即可。
轻量替代方案:短轮询(适合快速上线、流量极小的场景)
如果不想配置WebSocket和Redis,也可以用短轮询快速实现效果:
- 给帖子列表接口增加
since_id参数,接口只返回id大于传入值的最新帖子 - 前端写定时器,每15~30秒发起一次轻量请求,拉取当前页面最新帖子id之后的新内容,拿到结果后插入到页面即可
- 这个方案的缺点是有固定的内容延迟,且会产生额外的无效请求,只适合初期用户量极少的时候临时用。
落地注意事项
- 做广播的时候记得带上权限判断,非公开帖子不要推送给没有查看权限的用户
- 前端插入新内容前一定要做去重校验,避免网络重连导致同一条帖子重复渲染
- 可以在
app/channels/application_cable/connection.rb里配置当前登录用户的识别逻辑,拦截未登录用户的非法订阅请求
内容的提问来源于stack exchange,提问作者Abhimanyu Solanki
相关产品推荐
相关产品推荐

