Rails Actioncable для определенных страниц

В моем проекте у меня есть модель categories, которая имеет подробный вид (показать). Представление содержит данные, которые должны обновляться в реальном времени с помощью actioncable. Содержимое/данные представления не важны для понимания проблемы.

Стандартная установка, в которой у меня есть только одна категория с жестко закодированным идентификатором, работает отлично, но теперь я хочу сделать ее динамической и специфичной для страницы, чтобы мне не нужно было открывать 100 подписок, которые мне, возможно, даже не нужны, если я не на странице сведений о категориях.

Первая проблема: как создать соединения только для текущей страницы/категории?

Вторая проблема: как получить идентификатор текущей категории?

App.cable.subscriptions.create { channel: "RankingChannel", category_id: HOW_DO_I_GET_THIS}

Единственное, что я нашел, это это, но это не сработало: https://stackoverflow.com/a/36529282/5724835


person Daskus    schedule 20.09.2016    source источник


Ответы (5)


Каналы, похоже, не предназначены для конкретной страницы (точно так же, как контроллеры не привязаны к странице), но вы можете ограничить свой канал (как вы пытаетесь сделать), чтобы подписка получала сообщения только для данной категории.


Вы можете выборочно включить js-файл канала на страницу, поместив что-то вроде этого в заголовок файла макета:

<%= javascript_include_tag 'channels/'+params[:controller] %>

вместо того, чтобы делать //= require_tree ./channels в файле cables.js.

но у этого есть недостаток, заключающийся в том, что вам нужно включить файл js в массив предварительной компиляции в вашем инициализаторе config\initializers\assets.rb и перезапустить ваш сервер:

Rails.application.config.assets.precompile += %w( channels/categories.js )

Определение канала — это место, где вы ограничиваете поток, используя stream_for с моделью, что делает идентификатор соединения уникальным, например, categories:Z2lkOi8vcmFpbHMtc3RaaaRlci9Vc2VyLzI, а не просто categories.

приложение/каналы/ranking_channel.rb

class RankingChannel < ApplicationCable::Channel
  def subscribed
    category = Category.find(params[:category_id])
    stream_for category
  end

  ...

end

Вы отправляете category_id со страницы клиента на сервер. Здесь мы делаем это с помощью атрибута data-category-id элемента #category в DOM (с использованием синтаксиса jQuery, но его можно довольно легко преобразовать в обычный js).

приложение/активы/javascripts/каналы/categories.js

$( function() {
  App.categories = App.cable.subscriptions.create(
    {
      channel: "RankingChannel",
      category_id: $("#category").data('category-id')
    },{
      received: function(data) {
        ...
      }
    }
  );
});

Таким образом, на ваш взгляд, вам нужно будет указать идентификатор категории при создании страницы:

приложение/представления/категории/show.html.erb

<div id="category" data-category-id="<%= @category.id %>">
  <div class="title">My Category</div>

  ...

</div>
person clairity    schedule 22.09.2016
comment
Интересный ответ. Не могли бы вы объяснить, как работает плагин между clientToServer, поскольку вы создаете уникальную серверную часть ключа и просто используете клиентскую сторону id. Выполняет ли rails сопоставление автоматически? Еще одна вещь, это кажется небезопасным для личных данных (например, чата), поскольку пользователю просто нужно вручную изменить идентификатор данных, чтобы подключиться к другому каналу и получить новое сообщение, или, может быть, я что-то упустил. Спасибо. - person Mene; 06.10.2016
comment
@Patient55 Да, совпадение происходит автоматически, но в основном файл javascript и соответствующий файл .rb на стороне сервера имеют соответствующие методы, которые сервер ActionCable и код на стороне клиента склеивают для вас. Что касается безопасности, вы можете указать аутентификацию в методе subscribed файла ranking_channel.rb. Например, вы можете reject unless current_user.categories.include?(category) ограничить доступ пользователей к категориям, с которыми они связаны. - person clairity; 19.11.2016
comment
Спасибо, что нашли время ответить - person Mene; 25.11.2016
comment
Спасибо за Ваш ответ. Я также пришел к такому же подходу и описал его в своем блоге jademind.com/blog/posts/. - person anka; 19.08.2017

Что ж, для Rails 6 я решил это аналогично ответу @YaEvan выше.

Я поставил условие для всех моих *_channel.js файлов:

import consumer from "./consumer"

document.addEventListener('turbolinks:load', function() {
  // Because I was going to work with this element anyway, I used it to make the condition.
  var body = document.getElementById("news_body");
  if(body) {
    consumer.subscriptions.create("NewsChannel", {
      connected() {
        // Called when the subscription is ready for use on the server
      },

      disconnected() {
        // Called when the subscription has been terminated by the server
      },

      received(data) {
        // Called when there's incoming data on the websocket for this channel
      }
    });
  }
});

Это обходной путь. Я не нашел ответа, как правильно это сделать. Так что, если вы когда-нибудь обнаружите, пожалуйста, дайте мне знать!

person bonafernando    schedule 19.12.2019

Если вы хотите указать текущую страницу, вы должны добавить теги контроллера и действия внутри тега body.

использовать: data-controller="#{controller.controller_path}" data-action="#{controller.action_name в "тело"

Detail: Класс тела для контроллера в приложении Rails

А потом судить, есть ли указанная страница в js.

кофе:

jQuery(document).on 'turbolinks:load', ->
    if $('body').attr('data-controller') == 'special_controller' && $('body').attr('data-action') == 'special_action'
       App.server = App.cable.subscriptions.create "SpecialChannel",
       received: (data) -> 

js:

jQuery(document).on('turbolinks:load', function() {
    if ($('body').attr('data-controller') === 'special_controller' && 
        $('body').attr('data-action') === 'special_action') {
            return App.server = 
                App.cable.subscriptions.create("SpecialChannel", {
                    received: function(data) {}
                });
         }
 });

Иногда мы добавляем js только на определенную страницу, если какой-то файл js не очень большой, я рекомендую загрузить все js, оценивая страницу и поведение запуска js.

person YaEvan    schedule 13.04.2018

Другой подход, который я видел, - это украшение вашего html атрибутами данных, чтобы указать ожидаемое поведение (например, поместить идентификатор текущего ресурса в атрибут данных при посещении страницы шоу или что-то в этом роде). Таким образом, атрибут данных можно будет найти в вашем javascript и, если он будет найден, создать клиентскую часть подписки.

<div data-category="<%= @category.id %>">
  ...
</div>

Мне больше нравится этот подход, потому что он позволяет коду на стороне сервера оставаться ближе к коду по умолчанию, и для этого требуется только дополнительный оператор if в вашем javascript и дополнительный атрибут в вашем html.

Это действительно похоже на ссылку, которую вы упомянули в своем вопросе. Я бы предложил попробовать еще раз, поскольку мне удалось заставить его работать в моем собственном контексте/варианте использования.

person Jake Smith    schedule 14.10.2016

Я создал функцию, которую я могу вызывать, когда хочу инициировать соединение. Это дает дополнительное преимущество передачи дополнительного контекста в регистрацию.

registerSomeChannel = function(bleh) {
  console.log("Register some channel for bleh:", bleh);
  App.cable.subscriptions.create({
    channel: "SomeChannel",
    bleh: bleh
  }, {
    connected: function() {
      // Called when the subscription is ready for use on the server
      console.log("Connected to some channel", this);
    },

    disconnected: function() {
      // Called when the subscription has been terminated by the server
      console.log("Disconnected from some channel");
    },

    received: function(data) {
      // Called when there's incoming data on the websocket for this channel
      console.log("ActionCable received some data:", data);
    }
  });
}

Затем в файле JavaScript контроллера я могу сделать это:

$(document).ready(function(){
  // Register for some channel
  var bleh = "yay";
  registerSomeChannel(bleh);
});

Предупреждение: это мой первый раз, когда я действительно играю с ActionCable, так что YMMV.

person Barrett Clark    schedule 26.04.2017