【问题标题】:When would it be better to use `ActionCable::Channel::Streams#stream_from` over `#stream_for`?什么时候使用 `ActionCable::Channel::Streams#stream_from` 而不是 `#stream_for` 更好?
【发布时间】:2020-12-12 23:15:28
【问题描述】:

问题

是否存在stream_from 优于stream_for 的用例?

示例

在这个人为的示例中,我创建了一个ChatChannel,它可以连接到不同的rooms。每个版本的代码还包括向该房间广播消息的方法和subscribed 方法中的注释,显示房间名称应为room == '123'

使用stream_from

  class ChatChannel <  ApplicationCable::Channel
    def subscribed
      stream_from "chat_#{params[:room]}" #=> "chat_123"
    end
  end

  ActionCable.server.broadcast("chat_#{room}", data)

使用stream_for

  class ChatChannel <  ApplicationCable::Channel
    def subscribed
      stream_for params[:room] #=> "chat:123"
    end
  end

  ChatChannel.broadcast_to(room, data)

要指出的一件有趣的事情是,使用stream_for 方法,您仍然可以使用ActionCable.server.broadcast("chat:#{room}", data)(注意_ 更改为:)但您不能将ChatChannel.broadcast_tostream_from 一起使用接近。

推理

stream_from 更“危险”,因为它设置了一个全局房间,需要作者维护一个约定。另外,广播只能直接从ActionCable.server进行。

stream_for 命名空间和广播可以从频道的类方法broadcast_to 访问。虽然仍会创建全局房间,但命名约定由 Rails 维护。

最佳猜测

到目前为止,我发现的唯一区别是stream_from 将允许多个通道通过单个呼叫传输相同的消息。例如。如果我同时拥有ChatChannelNotificationChannel 并且都包含stream_from 'common',我将能够使用ActionCable.server.broadcast('common', data) 同时在两个频道上进行广播。不过,这对我来说似乎是一种代码味道。

【问题讨论】:

    标签: ruby-on-rails ruby publish-subscribe conventions actioncable


    【解决方案1】:

    使用stream_for 方法快速更正您的示例。 stream_for 需要一个模型,所以你的例子应该是

     class ChatChannel <  ApplicationCable::Channel
        def subscribed
          room = Room.find params[:room]
    
          stream_for room 
        end
      end
    

    注意:

    您的方法仍然有效,唯一的区别是流的命名广播。正如您很快就会看到的那样,这将更有意义。

    stream_forstream_from 之间有什么区别吗?我不会说太多,但要实事求是地回答这个问题,让我们看看它们在幕后是如何工作的。

    stream_for

    获取模型并反过来调用stream_from。结果是一个可序列化的字符串,用作命名广播。所以当你在一个

    上调用stream_from
    1. 模型,命名广播成为模型全局id
    2. hash,命名广播变成查询参数
    3. 字符串,广播的名字变成字符串本身

    所以 stream_for 像这样stream_for -&gt; broadcasting_for -&gt; stream_from

    broadcasting_for 是 ActionCable 用来为应用程序中的对象生成唯一命名广播的机制。这可确保每个对象在您的应用程序生命周期中都有一个唯一标识符。

    对于 ActiveRecord 模型,rails 在它们上调用to_gid_param,这会返回该模型实例的唯一标识符。如果您更新模型、更改属性、重新加载模型、在该模型上调用 to_gid_param 将始终返回相同的字符串。您可以在 Rails 控制台中尝试此操作,在 AR 模型实例上调用 to_gid_param

    对于其他类型的对象,rails 调用 to_param 将对象转换为 URL 安全查询参数。一种特殊情况是数组,请参阅how rails handle that here

    stream_from

    这里的根本区别是您不需要传递模型,只需传递一个字符串作为命名广播就可以了。如果你传递的不是字符串,it still gets converted to a string

    澄清你的疑虑

    需要指出的一件有趣的事情是,使用 stream_for 方法,您仍然可以使用 ActionCable.server.broadcast("chat:#{room}", data) (注意将 _ 更改为 :) 但您不能使用ChatChannel.broadcast_to 与 stream_from 方法。

    好吧,broadcast_to 最终还是会调用 ActionCable.server.broadcast,如果您希望在所有频道中都出现这种行为,您可以在基类中添加一个 broadcast 类方法,通常是 ApplicationCable::Channel

    
    module ApplicationCable
      class Channel < ActionCable::Channel::Base
        def self.broadcast(broadcasting, data)
          ActionCable.server.broadcast(broadcasting, data)
        end
      end
    end
    

    您甚至可以通过在此处统一 broadcast_to 来更进一步,因为您可以在您的频道中访问 braodcasting_for。在你的 Rails 控制台中尝试 ChatChannel.broadcasting_for(arg) 传递不同的值,包括模型,看看我的意思。

    stream_from 更“危险”,因为它设置了一个需要作者维护的约定的全局房间。此外,广播只能直接从 ActionCable.server 完成。

    就像我之前说的,您可以绕过这个,请参阅上面的简单解决方案。至于stream_for更危险,你怎么定义危险?如果您的意思是因为您必须自己维护命名广播,我将为您提供有意义的实例。

    在您的示例中,您说stream_from 设置了一个全局房间。

    请注意,不会建立订阅,除非您的客户端代码明确订阅您的频道。因此,即使您使用stream_for "chat_#{params[:room]}",除非您的客户端代码订阅房间,否则永远不会建立连接。

    如果我同时拥有 ChatChannel 和 NotificationChannel 并且都包含 stream_from 'common',我将能够使用 ActionCable.server.broadcast('common', data) 同时在两个频道上进行广播。不过,这对我来说似乎是一种代码味道。

    ActionCable 文档建议至少 a consumer should be subscribed to one channel。 你如何选择使用stream_from 会导致代码异味,这不是 ActionCable/Rails 的问题。

    什么时候应该使用stream_from

    如果您计划制作复杂的命名广播以根据角色或某些标准将消息路由到客户端,使用stream_from 可以帮助您。这将确保只有满足这些定义标准的用户才能获得广播。

    例子

    我想向房间里的每个人广播一条消息

     class ChatChannel <  ApplicationCable::Channel
        def subscribed
          room = Room.find_by_name params[:room]
    
          stream_for room 
        end
      end
    

    客户最终会像这样订阅这个频道

    consumer.subscriptions.create({ channel: "ChatChannel", room: "gaming" })
    

    每个客户都会收到此消息。

    如果您想在不寻求复杂解决方案的情况下强制实施某种安全性怎么办?我们可以使用stream_from 来实现某种隐私。假设您想向游戏室中的特定用户发送消息,我们可以像这样制作一个命名广播

     class ChatChannel <  ApplicationCable::Channel
        def subscribed
          stream_from "chat_#{params[:room]}_#{params[:user]}"
        end
      end
    

    客户最终会像这样订阅上述频道

    consumer.subscriptions.create({ channel: "ChatChannel", room: "gaming", user: "1" })
    

    这样如果你将消息广播到上述广播,只有 ID 为 1 的用户会收到消息。

    【讨论】:

    • 感谢您精彩而详尽的文章! ? 我一直在玩 AC,也遇到了一些使用 identified_by 的例子。如果您想自己管理频道和房间,stream_from 似乎是公开的基本方法(Rails 的“利器”),而 stream_for 和其他工具允许您遵循 Rails 对对象和用户的约定.
    猜你喜欢
    • 2016-12-24
    • 2012-06-15
    • 2011-06-26
    • 1970-01-01
    • 2014-10-08
    • 2012-01-15
    • 2013-05-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多