Rails中ActionCable推送时如何为Serializer注入current_user?
我来帮你搞定这个问题!你已经通过identified_by :current_user完成了ActionCable的用户认证,其实离解决问题只差一步——把这个current_user传递给序列化器就行。这里有几个实用的方案,你可以根据自己的场景选择:
方案一:针对每个订阅用户生成个性化序列化数据
因为每个ActionCable连接都绑定了current_user,我们可以遍历所有订阅了频道的连接,为每个用户单独生成包含is_voted的序列化数据,再逐个发送:
首先修改你的序列化器,改用context来获取用户(这是AMS推荐的传递上下文的方式):
class PostSerializer < ActiveModel::Serializer attribute :title attribute :is_voted do # 从context中取出current_user voteable_ids = context[:current_user].account.votes.where(voteable_type: 'Post').map(&:voteable_id) voteable_ids.include? object.id end end
然后在触发更新的地方(比如Post的after_update回调,或者业务逻辑里),替换原来的广播代码:
# 找到所有订阅了updates_channel的连接 UpdatesChannel.subscriptions.each do |subscription| next unless subscription.current_user.present? # 跳过未认证的连接 # 用当前连接的current_user序列化帖子 serialized_post = ActiveModelSerializers::SerializableResource.new( post, context: { current_user: subscription.current_user } ).as_json # 单独发送给这个用户的连接 subscription.send_data({ event: 'posts.updated', data: serialized_post }.to_json) end
这个方案的优势是直接利用已有的连接上下文,不需要额外的频道配置;缺点是如果订阅用户较多,遍历连接会有一定性能开销,适合用户量不大的场景。
方案二:为每个用户创建独立的订阅频道
这是Rails官方推荐的个性化广播方案——给每个用户分配专属的频道,广播时直接针对用户的频道发送个性化数据:
首先修改你的Channel类,让用户订阅自己专属的频道:
class UpdatesChannel < ApplicationCable::Channel identified_by :current_user def subscribed # 每个用户订阅自己的专属频道 stream_from "updates_channel_#{current_user.id}" end # 封装一个广播方法给后端调用 def self.broadcast_post_update(post) # 如果你只需要推送给相关用户(比如帖子的作者或关注者),可以替换成特定用户集合 User.find_each do |user| serialized_post = ActiveModelSerializers::SerializableResource.new( post, context: { current_user: user } ).as_json # 广播到该用户的专属频道 ActionCable.server.broadcast( "updates_channel_#{user.id}", event: 'posts.updated', data: serialized_post ) end end end
然后在触发更新的地方,调用这个封装好的方法:
UpdatesChannel.broadcast_post_update(post)
这个方案的优势是扩展性更好,用户量增长时性能更稳定;缺点是需要维护用户专属频道,前端订阅时也要对应连接到自己的频道(不过这一步很简单,前端只需要在订阅时带上当前用户ID即可)。
方案三:前端本地计算is_voted(简化版)
如果你的前端已经维护了当前用户的投票记录,那可以只广播帖子的基础数据,让前端自己判断is_voted状态:
广播时只发送必要字段:
ActionCable.server.broadcast 'updates_channel', { event: 'posts.updated', data: { id: post.id, title: post.title } }
前端收到事件后,用本地存储的用户投票ID列表,对比帖子ID来更新is_voted状态。这个方案最简单、性能最好,但前提是前端能保证投票状态的一致性,适合投票操作不频繁的场景。
内容的提问来源于stack exchange,提问作者mbajur

