[matt.mc]

Matt.tech_ramblings

Part 2: Real-Time Voting With PubNub

In part 1, we created a really simple and utterly useless voting application. If you’re following along, at this point you should have an application that displays a round of songs that you can vote on and never see them update unless you refresh the page. Now comes the fun part of tying everything together with PubNub.

If you don’t already have a PubNub account, you’re going to need to go to their site and create one. After you’ve signed up, make note of the Publish and Subscribe keys you see on your Account page, as you’ll be using those shortly.

Now that we’ve got a PubNub account ready, let’s start prepping the application for it. First we’ll need to add the PubNub gem to our Gemfile. Note: At this point we still need to use an old version of the gem due to a bug causing some problems with Thin, but I’ll update here once it’s resolved.

gem 'pubnub', '~> 0.1'

The version isn’t necessarily required, but I like to include them just in case. If you were curious, the version constraint ~> is identical to >= 3.3.0 and < 3.4. We can assume they won’t change anything that would break our code in versions 3.3.1 through 3.3.9, so we can allow it to update to those versions when we update our bundle.

Once you’ve added the gem and run bundle install, we can set up the necessary PubNub connections in our app. First let’s set up the publishing in the app. We need to instantiate a new PubNub object using at least the publish key from our PubNub account page. I like to do it above all the routes, so we’ll place it right above our root route. Make sure you switch out the publish and subscribe placeholders for your own!

app.rb
1
2
3
4
5
6
7
8
9
10
PUBNUB = Pubnub.new(
  'PUBLISH-KEY',
  'SUBSCRIBE-KEY',
  '', # Secret Key (optional)
  '', # Cipher Key (optional)
  false # Use SSL? (optional)
)

get '\' do

Now we just need to add the actual publish command to the vote route. We want to happen after a song and round have successfully been saved.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

if @song.save and @round.save
  @songs = Round.current.first.songs
  logger.info "Total votes: #{@round.total_votes}"

  publish = PUBNUB.publish({
    'channel' => 'votr-vote',
    'message' => { 'total_votes' => @round.total_votes, 'songs' => @songs },
    'callback' => lambda do |message|
      logger.info message
    end
  })

  render :success => true, :attributes => { 'round' => @round, 'songs' => @songs }

See how easy that was? Fire up the application using padrino s and vote on a few songs. If you want to be able to see the messages you’re sending, go to your PubNub console, make sure you connect to the correct channel (“votr-vote” if you used what I did) and then send some more votes. You should see something like this in your console.

PubNub Console

At this point, we’re able to accept votes and publish a message to our PubNub channel with the updated round stats. All that’s left is to write a little JavaScript to subscribe to that channel and update the round whenever a new message comes in.

views/layouts/application.haml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<div pub-key="PUBLISH-KEY" sub-key="SUBSCRIBE-KEY" ssl="off" origin="pubsub.pubnub.com" id="pubnub"></div>
%script{ :src => "http://cdn.pubnub.com/pubnub-3.1.min.js" }
- unless @round.blank?
 :javascript

   function disableVoting() {
     
   }

   function update_votes(message) {
     console.log("Message Recieved");
     total_votes = message.total_votes;
     $.each(message.songs, function(index, value) {
       $("#song_" + this.id + " .votes").text(this.votes);
     });
     update_percentages(message);
   }

   function update_percentages(message) {
     total_votes = message.total_votes;
     $.each(message.songs, function(index, value) {
       percentage = this.votes / total_votes;
       $("#song_" + this.id + " .progress .bar").width(percentage * 100 + "%");
     });
   }

   $(function () {

     $('#clock').countdown({until: #{@timer.to_i}, format: 'HMS', compact: true, onExpiry: disableVoting});

     update_votes(#{@msg});

     $('.vote').click(function(event) {
       
     });

     // LISTEN FOR MESSAGES
     PUBNUB.subscribe({
       channel    : "votr-vote",
       restore    : false,
       callback   : update_votes,
       disconnect : function() {        // LOST CONNECTION.
         console.log("Disconnected from PubNub.");
       },
       reconnect  : function() {        // CONNECTION RESTORED.
         console.log("Reconnected to PubNub.");
       },
       connect    : function() {        // CONNECTION ESTABLISHED.
         console.log("Connected to PubNub.");
       }
     })
   });

Let’s follow what’s happening step by step. Right when the page loads, update_votes(#{@msg}) is called. @msg is essentially the same message that gets sent via PubNub, so all we’re doing is sending the round as-is to the user right when the page loads. update_votes() then sets each song’s vote count and then calls update_percentages(), which is what actually sets the width of the progress bars. update_votes() then gets called every time a new message is received from PubNub.

Since we haven’t put any restrictions on the number of times you can vote, open up a few browser windows side by side and start clicking away. You should see all of your open windows updating simultaneously.

So there you have it! You’ll notice in the repository there’s an admin directory. This is a cool feature of Padrino that allows you to essentially drop in an admin interface by simply running padrino g admin. The admin interface in the repository has been modified to look like the rest of the application, but otherwise it’s largely similar to the generated version. For more info on the Padrino Admin generator see the Padrino guides.

Votr Admin Interface

Vote away and enjoy! If you have any questions or suggestions, don’t hesitate to drop me a line on Twitter @matt_mcclure.

Comments