什么时候使用 `ActionCable::Channel::Streams#stream_from` 比 `#stream_for` 更好?

When would it be better to use `ActionCable::Channel::Streams#stream_from` over `#stream_for`?

问题

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

例子

在这个人为的示例中,我创建了一个 ChatChannel,它可以连接到不同的 room。每个版本的代码还包括向该房间广播消息的方法以及 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 房间和广播的命名空间可以通过频道的 class 方法 broadcast_to 访问。虽然仍然创建了全局房间,但命名约定由 Rails 维护。

最佳猜测

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

使用 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 -> broadcasting_for -> stream_from

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

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

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

stream_from

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

解惑

One interesting thing to point out is that using the stream_for approach, you can still use ActionCable.server.broadcast("chat:#{room}", data) (note the change of _ to :) but you cannot use ChatChannel.broadcast_to with the stream_from approach.

好吧,broadcast_to 仍然会调用 ActionCable.server.broadcast 如果你想在你的所有频道中都有这种行为,你可以只添加一个 broadcast class 方法到你的基地 class 通常 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 is more "dangerous" as it sets a global room that requires a convention maintained by the author(s). Additionally, broadcasting can only be done directly from ActionCable.server.

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

In your example, you said stream_from sets a global room.

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

If I had both a ChatChannel and NotificationChannel and both contain stream_from 'common', I'd be able to broadcast on both channels at once with ActionCable.server.broadcast('common', data). This seems like a code smell to me though.

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的用户才能收到消息。