Есть ли вариант использования, когда 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_to
с подходом stream_from
.
stream_from
является более «опасным», поскольку устанавливает глобальную комнату, которая требует соглашения, поддерживаемого автором (авторами). Кроме того, трансляция может осуществляться только напрямую из ActionCable.server
.
stream_for
пространства имен, доступ к комнате и трансляции можно получить из метода класса канала broadcast_to
. Хотя глобальная комната все еще создается, соглашение об именах поддерживается Rails.
Единственное отличие, которое я обнаружил до сих пор, заключается в том, что stream_from
позволяет нескольким каналам передавать одно и то же сообщение с помощью одного вызова. Например. Если бы у меня были и ChatChannel
, и NotificationChannel
, и оба содержали бы 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_for
и stream_from
? Я не буду много говорить, но чтобы ответить на этот вопрос фактически, давайте посмотрим, как они оба работают под капотом.
Берет модель и перезванивает stream_from
. Результатом является сериализуемая строка, которая используется в качестве именованного широковещания. Итак, когда вы вызываете stream_from на
Итак, stream_for работает так stream_for -> broadcasting_for -> stream_from
broadcasting_for — это механизм, который ActionCable использует для создания уникальных именованных трансляций для объектов в вашем приложении. Это гарантирует, что каждый объект будет иметь уникальный идентификатор в жизненном цикле вашего приложения.
Для моделей ActiveRecord рельсы вызывают для них to_gid_param
, который возвращает уникальный идентификатор для этого экземпляра модели. Если вы обновите модель, измените атрибут, перезагрузите модель, вызов to_gid_param
для этой модели всегда будет возвращать одну и ту же строку. Вы можете попробовать это в своей консоли rails, вызвав to_gid_param
экземпляр модели AR.
Для других типов объектов rails вызывает to_param
, который преобразует объект в безопасные параметры запроса URL. Одним из особых случаев является Array, см. как рельсы справляются с этим здесь.
Фундаментальное отличие здесь в том, что вам не нужно передавать модель, просто передайте строку как именованное вещание, и все готово. Если вы передаете что-либо, кроме строки, оно все равно преобразуется в строку.
Следует отметить одну интересную вещь: используя подход 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
на вашем канале. Попробуйте ChatChannel.broadcasting_for(arg)
в консоли rails передать разные значения, включая модели, чтобы понять, что я имею в виду.
stream_from
более «опасный», поскольку он устанавливает глобальную комнату, которая требует соглашения, поддерживаемого автором (авторами). Кроме того, трансляция может осуществляться только непосредственно с ActionCable.server.
Как я уже говорил ранее, вы можете обойти это, см. простое решение выше. Что касается stream_for
быть более опасным, как бы вы определили опасный? Если вы имеете в виду, потому что вы должны сами поддерживать названное вещание, я приведу вам примеры, когда это имеет смысл.
В вашем примере вы сказали, что
stream_from
устанавливает глобальную комнату.
Обратите внимание, что подписка не будет установлена, за исключением случаев, когда ваш клиентский код явно подписывается на ваш канал. Таким образом, даже если вы используете stream_for "chat_#{params[:room]}"
, соединение никогда не будет установлено, если ваш клиентский код не подпишется на комнату.
Если бы у меня были и ChatChannel, и NotificationChannel, и оба содержали stream_from 'common', я мог бы транслировать сразу по обоим каналам с помощью ActionCable.server.broadcast('common', data). Хотя мне это кажется запахом кода.
Документы ActionCable рекомендуют, чтобы, по крайней мере, потребитель был подписан на один канал.
Как вы решите использовать 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 для объектов и пользователей.