如何从控制器或模型调用 ActionCable 方法 - "uninitialized constant QuizSession::App" 错误

How to call ActionCable method from controller or model - "uninitialized constant QuizSession::App" Error

我正在尝试用 Ruby 在 Rails 上与 2 个用户一起编写一个简单的测验应用程序,他们可以看到不同的观点:

我正在尝试使用 ActionCable 将这 4 个回答按钮广播到我的频道,但是当我尝试调用我在我的频道中定义的方法时,出现错误 "uninitialized constant QuizSession::App".

这些是我启用 ActionCable 所采取的步骤:

1) 我在 /app/channels/quiz_data_channel.rb:

中生成了一个新频道
class QuizDataChannel < ApplicationCable::Channel
  def subscribed
    stream_from "quiz_data"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def send_data
    Services::QuizDataCreation.new(user: current_user).create
  end
end

# /app/assets/javascripts/channels/quiz_data.coffee:
App.quiz_data = App.cable.subscriptions.create "QuizDataChannel",
  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
  $('answer-buttons').append(data.partial)

  send_data: ->
    @perform 'send_data'

2) 然后我在app/models/services/quiz_data_creation.rb:

新建了一个服务
module Services
  class QuizDataCreation

    def initialize(user)
      self.user = user
    end

    def create
      create_quiz_data
      broadcast_creation
    end

    private
    attr_accessor :user, :answers

    def create_quiz_data  #not sure if this will work
      @quiz_session = user.quiz_session
      self.answers = @quiz_session.quiz.questions[@quiz_session.current_question_index].answers
    end

    def broadcast_creation
      QuizDataBroadcastJob.perform_now(answers)
    end
  end
end

3) 然后我在 /app/jobs/quiz_data_broadcast_job.rb:

生成了一个新工作
class QuizDataBroadcastJob < ApplicationJob
  queue_as :default

  def perform(answers)
    ActionCable.server.broadcast('quiz_data', data: render_answer_options(answers))
  end

  private
  def render_answer_options(answers)
    answers.each do |answer|
      ApplicationController.render(
          #render student/quiz_question page and render as many answer_option partials as needed
          partial: 'pages/student/answer_option',
          locals: {answer: answer}
      )
    end
  end
end

4) 我在 routes.rb:

中安装了 ActionCable
mount ActionCable.server => '/cable'

5) 最后,我尝试通过在我的应用程序的其他地方调用 send_data 函数来广播数据:

def send_current_question
    App.quiz_data.send_data <--- this is apparently where the error gets thrown
end

我想知道的是:

我已经阅读了 3 个 ActionCable 指南并观看了 2 个指南视频 - 因为它们中的大多数似乎都是关于聊天应用程序的(在我看来是一对一的连接),我现在完全不知道如何获得我的应用程序以我需要的一对多广播方式工作。

我是 Rails 的新手,如有任何指点,我们将不胜感激! :)

WebSockets 的设计使得无论您是在构建一对一、多对一、一对多还是多对多功能都无关紧要。原因是作为一个频道的发布者,你不知道谁订阅了它,所以无论是1个还是1000个用户,他们都会收到发布的消息。

至于为什么会出现错误,App 对象是一个 JavaScript 对象(请参阅您如何在 coffeescript 文件中访问它),因此您不能在 Ruby代码。

App 对象在您的 Ruby 代码(后端)中不可用,因为它应该是客户端(用户)与服务器(后端)通信的一种方法,这就是它在您的 coffeescript 代码中初始化的原因。

因此您需要通过将点击处理程序附加到您的按钮来调用 send_data 方法。假设在您的 html 中,您的按钮看起来像:

<button id="broadcast-button">Broadcast</button>

在您的客户端代码 (coffeescript) 中,您将执行以下操作:

$('#broadcast-button').on 'click', (e) ->
  App.quiz_data.send_data()

所以当用户点击那个按钮时,send_data 方法将被调用。

As for why you are getting the error, well the App object is a JavaScript object (see how you are accessing it in your coffeescript files), so you cannot use it in Ruby code.

The App object is not made available in your Ruby code (backend) because it is supposed to be a method for the client (user) to communicate with the server (backend), which is why it is initialized in your coffeescript code.

正如@Laith Azer 指出的那样,不可能 从控制器或模型调用 CoffeeScript 方法(因为它代表前端),但是我所有的 CoffeeScript 方法 send_data 确实如此,就是在后端调用它的对应物:

#/app/assets/javascripts/channels/quiz_data.coffee:
App.quiz_data = App.cable.subscriptions.create "QuizDataChannel",
  ...
  send_data: ->
    @perform 'send_data'

那么可以,就是直接调用这个后端方法:

#In some controller or model:
QuizDataChannel.send_data(current_user)

为此,我们需要将 ActionCable 通道中的方法更改为 class 方法:

#/app/channels/quiz_data_channel.rb:
class QuizDataChannel < ApplicationCable::Channel
  def self.send_data(current_user) #self. makes it a class method
    new_quiz_html = Services::QuizDataCreation.new(user: current_user).create
    #change "some_room" to the room name you want to send the page on
    ActionCable.server.broadcast("some_room", html: new_quiz_html) 
  end
end

剩下的就是接收这个 html 页面并显示它:

#/app/assets/javascripts/channels/quiz_data.coffee:
App.quiz_data = App.cable.subscriptions.create "QuizDataChannel",
  ...
  send_data: ->
    @perform 'send_data'

  received: (data) ->
    # This will replace your body with the page html string:
    $('body').html(data.html)