问题
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