Skip to content

Instantly share code, notes, and snippets.

@supertinou
Last active September 8, 2015 23:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save supertinou/6f1e8fd4436471278238 to your computer and use it in GitHub Desktop.
Save supertinou/6f1e8fd4436471278238 to your computer and use it in GitHub Desktop.
Snippets for the Realtime quiz tutorial
git clone git@github.com:supertinou/livequiz.git && cd livequiz && git reset --hard SAMPLE-QUIZ-APP
gem 'pubnub', '~> 3.7'
gem 'pubnub-js', '~> 3.7'
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()
gem 'gon'
<%= include_gon %>
gon.push({
participant: @participant,
session_key: @session.access_key
})
@uuid = gon.participant.authorization_key
@auth_key = gon.participant.authorization_password
@session_key = gon.session_key
@participant = gon.participant
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
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
<%= react_component('ParticipantsList', participants: [] ) %>
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'))
@whoIsHereNow()
//= require pubnub
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'))
@subscribeToChatChannel()
<%= react_component('ParticipantsList', participants: @participant_list ) %>
@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
rails g AddCurrentQuestionIndexFieldToSessions current_question_index:integer
rake db:migrate
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
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
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 : ->
<%= react_component('QuestionDisplay') %>
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
require 'pubnub'
pubnub = Pubnub.new(
subscribe_key: 'demo',
publish_key: 'demo',
)
pubnub.publish(message: 'hello', channel: 'chan')
rails g model ParticipantAnswer participant:references answer:references
rake db:migrate
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
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
<%= react_component('ActivityFeed' ) %>
React.render(<ActivityFeed newActivity=message />, @react('ActivityFeed'))
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
})
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'))
switch(message.event)
when 'answered'
if message.data.uuid == @uuid
React.render(<SuccessNotifier success=message.data.correct />, @react('QuestionDisplay'))
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
switch(message.event)
when 'results' then React.render(<ResultsDisplay results=message.data.results />, @react('ResultsDisplay'))
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
require 'pubnub_singleton'
PubnubSingleton.client.publish(message: 'hello', channel: 'chan')
get 'quiz_sessions/:session_key/:authorization_key/:authorization_password', to: "quiz_sessions#show"
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
def quiz_session_link(participant)
path = "/quiz_sessions/#{participant.session.access_key}/#{participant.authorization_key}/#{participant.authorization_password}"
"http://"+ ENV['HOST']+path
end
<%= quiz_session_link(@participant) %>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment