适用于特定网页的Rails Actioncable

时间:2016-09-20 15:08:02

标签: ruby-on-rails ruby-on-rails-5 actioncable

在我的项目中,我有一个模型categories,它有一个详细视图(显示)。该视图包含应使用actioncable实时更新的数据。视图的内容/数据对于理解问题并不重要。

一个标准设置,我只有一个类别与硬编码ID完美,但现在我想让它动态和页面特定,所以我不需要打开100个订阅,我甚至没有如果我不在类别详细信息页面上,则需要。

第一个问题:如何仅为当前页面/类别创建连接?

第二个问题:如何获取当前类别的ID?

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

我发现的唯一的事情就是这个,但它不起作用: https://stackoverflow.com/a/36529282/5724835

5 个答案:

答案 0 :(得分:6)

频道似乎并非设计为特定于页面(就像控制器不是特定于页面的那样),但您可以调整频道的范围(正如您尝试的那样),以便订阅仅接收消息给定类别。

您可以通过在布局文件的头部放置这样的内容来有选择地在页面上包含频道的js文件:

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

而不是在//= require_tree ./channels文件中执行cables.js

但这样做的缺点是要求您在config\initializers\assets.rb初始化程序中的预编译数组中包含js文件并重新启动服务器:

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

频道定义是您使用stream_for与模型进行搜索范围的位置,这会使连接ID唯一,例如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从客户端页面发送到服务器。在这里,我们通过DOM中data-category-id元素的#category属性来执行此操作(使用jQuery语法,但可以很容易地转换为直接js)。

应用程序/资产/ Javascript角/信道/ categories.js

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

因此,在您的视图中,您需要在生成页面时包含类别ID:

应用程序/视图/类别/ show.html.erb

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

  ...

</div>

答案 1 :(得分:0)

我已经看到的另一种方法是使用数据属性来装饰你的html以表明行为期望(比如在访问节目页面时将当前资源的id放在数据属性中)。这样,可以在javascript中查找data属性,如果找到,则创建订阅客户端。

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

我更喜欢这种方法,因为它让服务器端代码更接近默认值,并且只需要在javascript中添加额外的if语句,并在html中添加其他属性。

这与您在问题中提到的链接非常类似。我建议再次尝试,因为我能够在我自己的上下文/用例中使用它。

答案 2 :(得分:0)

我创建了一个可以在我想触发连接时调用的函数。这样可以将额外的上下文传递给注册。

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。

答案 3 :(得分:0)

如果要指定当前页面,则应在body标记内添加控制器和操作标记。

在“body”中使用:data-controller="#{controller.controller_path}" data-action="#{controller.action_name

详细信息:Body class for controller in Rails app

然后判断是否在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的页面和行为。

答案 4 :(得分:0)

好吧,对于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
      }
    });
  }
});

这是一个解决方法。我没有找到正确的答案。因此,如果您发现了,请告诉我!