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

橙三吉。 提交于 2019-12-12 04:11:38

问题


I'm trying to write a simple quiz app with Ruby on Rails with 2 users, who get to see different views:

  • the quiz hoster view shows the current question and a "next question"-button (which he is supposed to project onto a wall for the audience) and
  • the participant view shows the 4 buttons with answer options corresponding to the current question (so the audience can participate in the quiz from their smartphones).

I'm at the point, where I'm trying to use ActionCable to broadcast these 4 answer buttons to my channel, but when I try calling the method I defined in my channel I get the error "uninitialized constant QuizSession::App".

These are the steps I've taken to enable ActionCable:

1) I generated a new channel in /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) Then I made a new service in 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) Then I generated a new job at /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) I mounted ActionCable in my routes.rb:

mount ActionCable.server => '/cable'

5) And finally I'm trying to broadcast data by calling the send_data function elsewhere in my application:

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

What I would like to know is:

  • How do I solve this error?
  • Is the problem that I haven't established the socket connection correctly?

I have read 3 ActionCable guides and watched 2 guide videos - since most of them seem to be about chat applications (which in my mind is a 1 to 1 connection) I am now thoroughly confused as to how to get my app to work in the one-to-many broadcasting fashion I need.

I'm new to Rails, so any pointers would be appreciated! :)


回答1:


WebSockets are designed such that it doesn't matter whether you are building a one-to-one, many-to-one, one-to-many or many-to-many feature. The reason is as a Publisher to a channel, you do not know who is Subscribed to it, so whether it is just 1 or 1000 users, they will all receive the message that is published.

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.

So you will need to call the send_data method by attaching a click handler to your button. Let's say in your html your button looks like:

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

In your client side code (coffeescript), you would do the following:

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

So when the user clicks on that button, the send_data method will be called.




回答2:


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.

As @Laith Azer pointed out, it is not possible to call a CoffeeScript method (since that represents the frontend) from a controller or model, but all my CoffeeScript method send_data really does, is call its counterpart on the backend:

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

So what is possible, is to call this backend method directly:

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

For that to work we need to change the method in our ActionCable channel to a class method:

#/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

And all that is left is to receive this html page and display it:

#/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)


来源:https://stackoverflow.com/questions/44011075/how-to-call-actioncable-method-from-controller-or-model-uninitialized-constan

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!