Когда лучше использовать `ActionCable::Channel::Streams#stream_from` вместо `#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_to с подходом stream_from.

Рассуждение

stream_from является более «опасным», поскольку устанавливает глобальную комнату, которая требует соглашения, поддерживаемого автором (авторами). Кроме того, трансляция может осуществляться только напрямую из ActionCable.server.

stream_for пространства имен, доступ к комнате и трансляции можно получить из метода класса канала broadcast_to. Хотя глобальная комната все еще создается, соглашение об именах поддерживается Rails.

Лучшая догадка

Единственное отличие, которое я обнаружил до сих пор, заключается в том, что stream_from позволяет нескольким каналам передавать одно и то же сообщение с помощью одного вызова. Например. Если бы у меня были и ChatChannel, и NotificationChannel, и оба содержали бы stream_from 'common', я мог бы вести трансляцию одновременно на обоих каналах с ActionCable.server.broadcast('common', data). Хотя мне это кажется запахом кода.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
611
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Быстрая коррекция на вашем примере с подходом 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_for

Берет модель и перезванивает stream_from. Результатом является сериализуемая строка, которая используется в качестве именованного широковещания. Итак, когда вы вызываете stream_from на

  1. модель, названная трансляция становится глобальным идентификатором модели
  2. hash, именованная трансляция становится параметром запроса
  3. строка, трансляция имени становится самой строкой

Итак, 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_from

Фундаментальное отличие здесь в том, что вам не нужно передавать модель, просто передайте строку как именованное вещание, и все готово. Если вы передаете что-либо, кроме строки, оно все равно преобразуется в строку.

Прояснение ваших сомнений

Следует отметить одну интересную вещь: используя подход 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 для объектов и пользователей.

Aaron 02.01.2021 17:58

Другие вопросы по теме