Rails Как использовать ActionCable для отображения всей новой страницы при получении:

Я пытаюсь создать приложение-викторину (не одностраничное приложение) с двумя типами пользователей и этим вариантом использования:

  1. пользователь-организатор-викторины и пользователи-участники находятся на "странице ожидания" до начала викторины
  2. пользователь-хозяин викторины нажимает кнопку "Начало викторины", чтобы начать викторину
  3. это немедленно заставляет всех пользователей-участников быть перенаправленными на новую страницу с вопросом викторины

Я следовал руководству и настроил ActionCable в своем приложении, но я хотел бы знать, как выполнить шаг 3. Мой текущий файл coffeescript для моего канала выглядит так:

# \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
    if current_student
      # HERE I want to render the page "quiz_question.html.erb"

      # ...and then I want to append the answer buttons:
      $('answer-buttons').append(data.partial)

  send_data: ->
    @perform 'send_data'

Я почти уверен, что ответ очень прост, но я гуглил множество руководств по Coffeescript и ActionCable, и почти все они отображают только частичные страницы на странице, на которой пользователь уже находится. Я новичок в Rails и ничего не знаю о Coffeescript, поэтому буду признателен за любую помощь!

ИЗМЕНИТЬ:

Вот как выглядит мой файл coffeescript после попытки следовать ответу Laiths:

App.quiz_data = App.cable.subscriptions.create "QuizDataChannel",
  connected: ->
    # Called when the subscription is ready for use on the server
    $('#btn btn-primary btn-lg').on 'click', (e) -> App.quiz_data.send_data()

  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
    if current_student
      pageHtml = data.page_html
      answerHtml = data.answer_html

      # This will replace your body with the page html string:
      $('body').html(pageHtml)

      # Then add your answer buttons:
      #$('#answer-buttons').html(answerHtml)
      $('answer-buttons').append(answerHtml)

  send_data: ->
    @perform 'send_data'

И это задание, которое я создал для рендеринга своих партиалов и их трансляции:

# app\assets\jobs\quiz_data_broadcast_job.rb:
class QuizDataBroadcastJob < ApplicationJob
  queue_as :default

  def perform(answers)
    ActionCable.server.broadcast('quiz_data', {
        page_html: render_page,
        answer_html: render_answer_options(answers)
    })
  end

  private
  def render_page
    ApplicationController.render(
        partial: 'pages/student/quiz_question',
        locals: {}
    )
  end

  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

РЕДАКТИРОВАТЬ 2:

Вот что говорит моя консоль Javascript:

Uncaught ReferenceError: App is not defined
    at quiz_data.self-21fd077347e9c34e83bab1a2d43a8db5b083fff7ed4eaa02e5314aa78f1dba8b.js:2
    at quiz_data.self-21fd077347e9c34e83bab1a2d43a8db5b083fff7ed4eaa02e5314aa78f1dba8b.js:23

И это то, что показывает мне, когда я нажимаю на него:

1    (function() {
2      App.quiz_data = App.cable.subscriptions.create("QuizDataChannel", {
3        connected: function() {
4          return $('#btn btn-primary btn-lg').on('click', function(e) {
5            return App.quiz_data.send_data();
6          });
7        },
8        disconnected: function() {},
9        received: function(data) {
10         var answerHtml, pageHtml;
11         if (current_student) {
12           pageHtml = data.page_html;
13           answerHtml = data.answer_html;
14           $('body').html(pageHtml);
15           return $('answer-buttons').append(answerHtml);
16         }
17       },
18       send_data: function() {
19         return this.perform('send_data');
20       }
21     });
22    
23   }).call(this);

EDIT 3: Это мой cable.js:

// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the rails generate channel command.
//
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);

person megahra    schedule 25.05.2017    source источник
comment
Вы создаете и страницу вопросов, и кнопки ответов после того, как организатор нажимает кнопку «Пуск»?   -  person Laith Azer    schedule 26.05.2017
comment
Да, это план @Laith   -  person megahra    schedule 26.05.2017


Ответы (1)


WebSockets на самом деле не предназначены для перенаправления HTTP. Тем не менее, вы все еще можете создать впечатление, что страница изменилась с помощью некоторого jQuery.

Поскольку вы будете создавать и вопрос, и ответы после того, как ведущий кликнет, вы можете отправить их оба в своей трансляции.

Сначала я бы предложил изменить файл quiz_question.html.erb на частичный: _quiz_question.html.erb, чтобы вы могли сгенерировать HTML и добавить его в свое тело. Затем включите элемент answer-buttons в файл _quiz_question, чтобы вы могли получить его с помощью jquery.

Таким образом, это будет выглядеть примерно так, но будет варьироваться в зависимости от вашей точной реализации:

# First create the question HTML partial (ensure this has the answer-
# buttons element inside it):
question_html = ApplicationController.render(partial: 
'quizzes/_quiz_question', locals: {question: @question})

# Next create the answer HTML partial:
answer_html = ApplicationController.render(partial: 
'answers/_answe_buttonr', locals: {answer: @answer})

# Broadcast both to your channel:
ActionCable.server.broadcast('QuizDataChannel', {
  question_html: question_html
  answer_html: answer_html
})

# Finally, handle this using jquery in your coffeescript:
received: (data) ->
  questionHtml = data.question_html
  answerHtml = data.answer_html

  # This will replace your body with the question html string:
  $('body').html(questionHtml)

  # Then add your answer buttons:
  $('#answer-buttons').html(answerHtml)
person Laith Azer    schedule 26.05.2017
comment
Спасибо за подробные предложения @Laith. Я интегрировал первую часть (настроенную для моего проекта) в метод connected, а вторую часть — в метод received моего файла coffeescript. Но теперь я получаю Uncaught ReferenceError: приложение не определено - я удалил все попытки вызвать приложение из моего кода Ruby после вашей помощи с моим предыдущий вопрос, поэтому я немного запутался. - person megahra; 28.05.2017
comment
Вы получаете это в своей консоли javascript или в журналах сервера? - person Laith Azer; 28.05.2017
comment
Консоль Javascript @Laith - person megahra; 28.05.2017
comment
Это правильное место, где вы должны звонить App. (в вашем coffeescript, который транспилируется в javascript). Если вы нажмете на нее, она покажет вам, где ошибка? - person Laith Azer; 28.05.2017
comment
Похоже, файл cable.js по какой-то причине не запускается. Этот файл есть в вашем проекте и содержит ли он следующее: (function() { this.App || (this.App = {}); App.cable = ActionCable.createConsumer(); }).call(this);? - person Laith Azer; 28.05.2017
comment
Да, cable.js лежит в app\assets\javascripts\ и содержит именно этот код @Laith - person megahra; 29.05.2017
comment
Давайте продолжим обсуждение в чате. - person megahra; 29.05.2017