【问题标题】:Rails Actioncable for specific pages特定页面的 Rails Actioncable
【发布时间】:2016-09-20 15:08:01
【问题描述】:

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

一个标准设置,其中我只有一个带有硬编码 id 的类别可以完美运行,但现在我想让它成为动态的和特定于页面的,这样我就不需要打开 100 个我什至不需要的订阅,如果我不在类别详细信息页面上。

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

第二个问题:如何获取当前分类的id?

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

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

【问题讨论】:

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


    【解决方案1】:

    频道似乎没有被设计为特定于页面(就像控制器不是特定于页面一样),但您可以确定频道的范围(正如您尝试做的那样),以便订阅只接收给定类别的消息.


    您可以通过在布局文件的头部放置类似的内容来选择性地将频道的 js 文件包含在页面中:

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

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

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

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

    通道定义是您确定流的范围,使用带有模型的 stream_for,这使得连接 ID 是唯一的,例如 categories:Z2lkOi8vcmFpbHMtc3RaaaRlci9Vc2VyLzI 而不仅仅是 categories

    app/channels/ranking_channel.rb

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

    您将category_id 从客户端页面发送到服务器。在这里,我们通过 DOM 中 #category 元素上的 data-category-id 属性来实现(使用 jQuery 语法,但可以很容易地转换为直接 js)。

    app/assets/javascripts/channels/categories.js

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

    因此,在您看来,您需要在生成页面时包含类别 ID:

    app/views/categories/show.html.erb

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

    【讨论】:

    • 有趣的答案。您是否介意解释一下 clientToServer 之间的插件是如何工作的,因为您生成了一个唯一的密钥服务器端并且只使用了 id 客户端。 rails 会自动匹配吗?还有一件事,这对于私人数据(如聊天)似乎是不安全的,因为用户只需手动更改数据 ID 即可插入另一个频道并接收新消息,或者我可能遗漏了一些东西。谢谢。
    • @Patient55 是的,匹配是自动的,但基本上 javascript 文件和相应的服务器端 .rb 文件都有相应的方法,ActionCable 服务器和客户端代码为您粘合在一起。至于安全性,您可以在ranking_channel.rb 文件的subscribed 方法中指定身份验证。例如,您可以reject unless current_user.categories.include?(category) 限制用户访问与他们有关联的类别。
    • 感谢您抽出宝贵时间回答
    • 感谢您的回答。我也以相同的方法结束并在我的博客文章jademind.com/blog/posts/… 中进行了描述。
    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

      如果你想指定当前页面,你应该在body标签里面添加controller和action标签。

      在“正文”中使用: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】:

        我见过的另一种方法是用数据属性装饰您的 html 以指示行为预期(例如在访问显示页面或其他内容时将当前资源的 id 放入数据属性中)。这样,可以在你的 javascript 中查找 data 属性,如果找到,则创建订阅客户端。

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

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

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

        【讨论】:

          【解决方案5】:

          我创建了一个函数,当我想触发连接时可以调用它。这提供了将额外上下文传递到注册中的额外好处。

          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。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2022-08-12
            • 2015-12-06
            • 1970-01-01
            • 2017-05-05
            • 2012-01-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多