User Presence with Rails using PrivatePub and Redis
Rails is a really nice framework for developing websites, however if you want real-time communication Rails will not be the best option. Socket.IO and Node.js seem to be a better alternative.
Despite that sometimes you have to add real time communications to your Rails app.In this article we use private pub which is a pub/sub gem along with redis database to persist user and preserve user’s online status in a rails application.
Add these gems to your Gemfile.
gem 'private_pub'
gem 'thin'
gem 'redis
Now use bundler to install new gems.
bundle install
Run the generator to create the private pub configuration files.
rails g private_pub:install
Add the JavaScript file to your application.js file manifest.
//= require private_pub
We will use one global channel ‘/presence’ and one private channel for each user ‘/push/:user_id’, the user should subscribe to them both on login.
‘/presence’ will be a channel for all online users to subscribe, while subscribing to a private channel will publish that this particular user is now logged_in/out to all online users after saving that user online status to redis database.
Now to bind the subscription and unsubscription events on a channel we need to change the privatepub.ru file to look like this
# Run with: rackup private_pub.ru -s thin -E production
require "bundler/setup"
require "yaml"
require "faye"
require "private_pub"
require "redis"
# you must pass any environment variables manually
redis = Redis.new
# delete online records
online_users = redis.keys "user_*_online"
redis.del *online_users unless online_users.empty?
puts 'All users are now offline!'
Faye::WebSocket.load_adapter('thin')
PrivatePub.load_config(File.expand_path("../config/private_pub.yml", __FILE__), ENV['environment'] || 'default')
faye_application = PrivatePub.faye_app(timeout: 30, ping: 20)
# subscribe - online
faye_application.bind(:subscribe) do |client_id, channel|
puts "Client subscribe: #{client_id}:#{channel}"
unless channel == "/presence"
user_id = channel.split('/').last
redis.set("user_#{user_id}_online", true)
Thread.new do
PrivatePub.publish_to "/presence", {:user_id => user_id, :status => "online"}
end
end
end
# unsubscribe - offline
faye_application.bind(:unsubscribe) do |client_id, channel|
puts "Client unsubscribe: #{client_id}:#{channel}"
unless channel == "/presence"
user_id = channel.split('/').last
redis.set("user_#{user_id}_online", false)
Thread.new do
PrivatePub.publish_to "/presence", {:user_id => user_id, :status => "offline"}
end
end
end
run faye_application
User records are deleted on Faye startup in case of Faye crash.
Now in the user’s web page, we can toggle the online/offline icon accordingly.
<% if (user_signed_in? && !(current_user.online?)) %>
<%= subscribe_to "/push/#{current_user.id}" %>
<%= subscribe_to "/presence" %>
PrivatePub.subscribe("/presence", function(data, channel) {
if($("#users").length > 0){
if(data.status == "online"){
$("#user-"+data.user_id+"-presence").removeClass("offline").addClass("online");
}else{
$("#user-"+data.user_id+"-presence").removeClass("online").addClass("offline");
}
}
});
<% end %>
Latly, start up Faye server using the rackup file that was generated.
rackup private_pub.ru -s thin -E production
Now you have an application that keeps the user’s online status in real time.
Authored by Amr Kamel
