-
-
Save supertinou/6f1e8fd4436471278238 to your computer and use it in GitHub Desktop.
Snippets for the Realtime quiz tutorial
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
git clone [email protected]:supertinou/livequiz.git && cd livequiz && git reset --hard SAMPLE-QUIZ-APP |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gem 'pubnub', '~> 3.7' | |
gem 'pubnub-js', '~> 3.7' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class LiveQuiz | |
constructor: () -> | |
@heartbeat = 20 | |
@uuid = ............. | |
@auth_key = ........ | |
@session_key = ............ | |
@participant = .............. | |
@client_channel = @session_key+"-client" | |
@server_channel = @session_key+"-server" | |
@chat_channel = @session_key+"-chat" | |
@pubnub = PUBNUB( | |
publish_key: '<%= ENV.fetch('PUBNUB_PUBLISH_KEY') %>' | |
subscribe_key: '<%= ENV.fetch('PUBNUB_SUBSCRIBE_KEY') %>' | |
auth_key: @auth_key | |
uuid: @uuid | |
origin: 'pubsub.pubnub.com' | |
ssl: true | |
) | |
##################### UTILITIES ######################## | |
# Fetch a react component from the react name | |
react: (react_name) -> | |
$("[data-react-class=#{react_name}]").get(0) | |
@liveQuiz = new LiveQuiz() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gem 'gon' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= include_gon %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
gon.push({ | |
participant: @participant, | |
session_key: @session.access_key | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@uuid = gon.participant.authorization_key | |
@auth_key = gon.participant.authorization_password | |
@session_key = gon.session_key | |
@participant = gon.participant |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
after_commit :set_forbidden_access_to_session_channels, on: [:create,:destroy] | |
after_commit :allow_full_rights_to_channels_to_quiz, on: [:create] | |
def server_channel | |
"#{access_key}-server" | |
end | |
def client_channel | |
"#{access_key}-client" | |
end | |
def chat_channel | |
"#{access_key}-chat" | |
end | |
def set_forbidden_access_to_session_channels | |
[server_channel,client_channel,chat_channel].each do |chan| | |
PubnubSingleton.client.grant(http_sync: true, channel: chan, read: false, write: false){|envelope|} | |
end | |
end | |
def allow_full_rights_to_channels_to_quiz | |
[server_channel,client_channel].each do |chan| | |
PubnubSingleton.client.grant(http_sync: true, channel: chan, presence: chan, auth_key: auth_key, read: true, write: true){|envelope| puts envelope.payload} | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
after_commit :grant_access_to_session_channels, on: :create | |
after_commit :revoke_access_to_session_channels, on: :destroy | |
def grant_access_to_session_channels | |
PubnubSingleton.client.grant(channel: self.session.server_channel, auth_key: self.authorization_password , read: true, write: false){|envelope|} | |
PubnubSingleton.client.grant(channel: self.session.client_channel, auth_key: self.authorization_password , read: false, write: true){|envelope|} | |
PubnubSingleton.client.grant(channel: self.session.chat_channel, presence: self.session.chat_channel, auth_key: self.authorization_password){|envelope|} | |
end | |
def revoke_access_to_session_channels | |
PubnubSingleton.client.revoke(channel: self.session.server_channel, auth_key: self.authorization_password){|envelope|} | |
PubnubSingleton.client.revoke(channel: self.session.client_channel, auth_key: self.authorization_password){|envelope|} | |
PubnubSingleton.client.revoke(channel: self.session.chat_channel, presence: self.session.chat_channel, auth_key: self.authorization_password){|envelope|} | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= react_component('ParticipantsList', participants: [] ) %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
whoIsHereNow: -> | |
@pubnub.here_now | |
channel: @chat_channel | |
state: true | |
callback: (message) => | |
participants = _.map(message.uuids, (participant) -> | |
{ uuid: participant.uuid, status: 'online', name: participant.state.name, email: participant.state.email } | |
React.render(<ParticipantsList participants=participants />, @react('ParticipantsList')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@whoIsHereNow() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//= require pubnub |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
subscribeToChatChannel: -> | |
@pubnub.subscribe | |
channel: @chat_channel | |
state: | |
name: @participant.name | |
email: @participant.email | |
message: -> | |
presence: @presenceCallback | |
connect: -> | |
heartbeat: @heartbeat | |
presenceCallback: (message) -> | |
status = switch message.action | |
when 'leave' then 'offline' | |
when 'timeout' then 'offline' | |
when 'join' then 'online' | |
data = message.data || {} | |
participants = [{uuid: message.uuid, status: status, name: data.name, email: data.email }] | |
React.render(<ParticipantsList participants=participants />, @react('ParticipantsList')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@subscribeToChatChannel() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= react_component('ParticipantsList', participants: @participant_list ) %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@participant_list = @session.participants.to_a.collect do |participant| | |
status = (@participant.id == participant.id) ? 'online' : '' | |
{ uuid: participant.authorization_key, status: status , name: participant.name, email: participant.email } | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rails g AddCurrentQuestionIndexFieldToSessions current_question_index:integer | |
rake db:migrate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def start! | |
self.current_question_index = 0 | |
send_current_question() | |
schedule_switch_to_next_question!(30) | |
save() | |
end | |
def schedule_switch_to_next_question!(secondes) | |
Rufus::Scheduler.singleton.in "#{secondes}s" do | |
ActiveRecord::Base.connection_pool.with_connection do | |
if switch_to_next_question! | |
schedule_switch_to_next_question!(secondes) | |
else | |
finish! | |
end | |
end | |
end | |
end | |
def send_current_question | |
send_event_with_data('question', {question: current_question.format(:title_with_answers)} ) | |
end | |
def send_event_with_data(event, data) | |
message = {event: event, data: data} | |
PubnubSingleton.client.publish(message: message, channel: self.server_channel, auth_key: auth_key){|envelope|} | |
end | |
def switch_to_next_question! | |
next_question_index = self.current_question_index + 1 | |
next_question_exist = self.quiz.questions[next_question_index] | |
succeeded_to_switch = if !next_question_exist.nil? | |
self.current_question_index = next_question_index | |
send_current_question() | |
save() | |
else | |
false | |
end | |
return succeeded_to_switch | |
end | |
def finish! | |
## The quiz is finished, do whatever you want! | |
self.current_question_index = nil | |
end | |
def current_question | |
current_question_index ? self.quiz.questions[current_question_index] : nil | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def format(format_name) | |
format = if format_name == :title_with_answers | |
h = { title: title } | |
h[:answers] = answers.to_a.collect{|answer| {id: answer.id, title: answer.title }} | |
h | |
else | |
raise 'Unknown format' | |
end | |
format | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
subscribeToServerChannel: -> | |
@pubnub.subscribe( | |
channel: @server_channel | |
message: @serverCallback | |
connect: -> | |
) | |
serverCallback: (message, env, ch, timer, magic_ch) => | |
switch(message.event) | |
when 'question' then React.render(<QuestionDisplay question=message.data.question />, @react('QuestionDisplay')) | |
### Commands called from the QuestionDisplay React component ### | |
answerQuestion: (id) -> | |
@sendEvent('answer', {answer_id: id}) | |
sendEvent: (event, data) -> | |
@pubnub.publish | |
channel: @client_channel | |
message: { event: event, auth_key: @auth_key, data: data} | |
callback : -> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= react_component('QuestionDisplay') %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def subscribe_to_client_events | |
PubnubSingleton.client.subscribe( | |
channel: client_channel, | |
auth_key: auth_key, | |
callback: handle_client_events | |
) | |
end | |
def handle_client_events | |
lambda do |envelope| | |
m = envelope.message | |
case m['event'] | |
when 'answer' | |
handle_question_answer(m['auth_key'], m['data']['answer_id']) | |
end | |
end | |
end | |
def handle_question_answer(auth_key, answer_id) | |
# Do whatever you want | |
# Maybe store the answers in the database ? | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'pubnub' | |
pubnub = Pubnub.new( | |
subscribe_key: 'demo', | |
publish_key: 'demo', | |
) | |
pubnub.publish(message: 'hello', channel: 'chan') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rails g model ParticipantAnswer participant:references answer:references | |
rake db:migrate |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def handle_question_answer(auth_key, answer_id) | |
ActiveRecord::Base.connection_pool.with_connection | |
participant = Participant.find_by(authorization_password: auth_key) | |
answer = Answer.find(answer_id) | |
allowed_to_answer_question = ( answer.question.id == current_question.id ) | |
if allowed_to_answer_question && !participant.have_already_answered_the_question?(answer.question) | |
participant.answer_question(current_question, answer) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def have_already_answered_the_question?(question) | |
question.participant_answers.where(participant_id: self.id).count >= 1 | |
end | |
def answer_question(question, answer) | |
participant_answers.build(answer: answer) | |
save() | |
answer.correct? | |
end | |
def number_of_correct_answers | |
participant_answers.joins(:answer).where({answers: {correct: true}}).count | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= react_component('ActivityFeed' ) %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
React.render(<ActivityFeed newActivity=message />, @react('ActivityFeed')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
answered_correctly = participant.answer_question(current_question, answer) | |
send_event_with_data('answered', { | |
uuid: participant.authorization_key, | |
name: participant.name, | |
timestamp: Time.now.to_i, | |
answered_correctly | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
switch(message.event) | |
when 'answered' | |
activity = { | |
action: message.event | |
timestamp: message.data.timestamp | |
uuid: message.data.uuid | |
data: | |
name: message.data.name | |
correct: message.data.correct | |
} | |
React.render(<ActivityFeed newActivity=activity />, @react('ActivityFeed')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
switch(message.event) | |
when 'answered' | |
if message.data.uuid == @uuid | |
React.render(<SuccessNotifier success=message.data.correct />, @react('QuestionDisplay')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def finish! | |
send_results() | |
self.current_question_index = nil | |
save() | |
end | |
def results | |
participants.collect do |participant| | |
{ | |
points: participant.number_of_correct_answers, | |
uuid: participant.authorization_key, | |
name: participant.name, | |
email: participant.email, | |
correct_answers_number: participant.number_of_correct_answers, | |
wrong_answers_number: participant.number_of_wrong_answers | |
} | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
switch(message.event) | |
when 'results' then React.render(<ResultsDisplay results=message.data.results />, @react('ResultsDisplay')) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'singleton' | |
class PubnubSingleton | |
include Singleton | |
attr_accessor :pubnub | |
def initialize() | |
@pubnub = Pubnub.new( | |
publish_key: ENV['PUBNUB_PUBLISH_KEY'], | |
subscribe_key: ENV['PUBNUB_SUBSCRIBE_KEY'], | |
secret_key: ENV['PUBNUB_SECRET_KEY'], | |
logger: Rails.logger | |
) | |
end | |
def self.client | |
self.instance.pubnub | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'pubnub_singleton' | |
PubnubSingleton.client.publish(message: 'hello', channel: 'chan') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
get 'quiz_sessions/:session_key/:authorization_key/:authorization_password', to: "quiz_sessions#show" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class QuizSessionsController < ApplicationController | |
def show | |
@participant = Participant.find_by(authorization_key: params[:authorization_key], authorization_password: params[:authorization_password] ) | |
@session = Session.find_by(access_key: params[:session_key]) | |
if @participant.nil? || (@session.id != @participant.session ) | |
redirect_to root_path, notice: 'You are not authorized to access this session' | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
def quiz_session_link(participant) | |
path = "/quiz_sessions/#{participant.session.access_key}/#{participant.authorization_key}/#{participant.authorization_password}" | |
"http://"+ ENV['HOST']+path | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= quiz_session_link(@participant) %> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment