As I continue to learn Rails, this is my experience developing a Rails application using Action Cable (allows communication using web sockets)
Finished product: Story Estimate
Assuming we already generated a new Rails app, lets start with authentication using Devise gem.
Add “gem ‘devise'” to your gem file.
Run a few commands to generate your users:
$ rails generate devise:install
$ rails generate devise User
$ rails generate devise:views
$ rails db:migrate
For the sake of this application, I want to enable guest users. Lets allow that:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def current_or_guest_user
current_user || guest_user
end
# find guest_user object associated with the current session,
# creating one as needed
def guest_user(with_retry = true)
# Cache the value the first time it's gotten.
@cached_guest_user ||= User.find(session[:guest_user_id] ||= create_guest_user.id)
rescue ActiveRecord::RecordNotFound # if session[:guest_user_id] invalid
session[:guest_user_id] = nil
guest_user if with_retry
end
private
def create_guest_user
u = User.create(email: "guest_#{Time.now.to_i}#{rand(100)}@example.com")
u.save!(validate: false)
session[:guest_user_id] = u.id
u
end
end
Now, if we want to sign in a user as a guest, we’d call sign_in(current_or_guest_user)
Next thing, we want to authorize our Action Cable connections with Devise users. In order to do that, we need to add some hooks in the config/initialazers. This will add current user id to user’s cookie:
config/initialazers/warden_hooks.rb
Warden::Manager.after_set_user do |user,auth,opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = user.id
end
Warden::Manager.before_logout do |user, auth, opts|
scope = opts[:scope]
auth.cookies.signed["#{scope}.id"] = nil
end
With this, we can configure rails connection:
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
protected
def find_verified_user
if verified_user = User.find_by(id: cookies.signed['user.id'])
verified_user
else
reject_unauthorized_connection
end
end
end
end
Now, time to generate a new channel:
rails g channel channel_name optional_method
It may look someting like this:
class CoolChannel < ApplicationCable::Channel
BASE_CHANNEL = 'BaseChannel'.freeze
def subscribed
stream_from "#{BASE_CHANNEL}#{params['some_key']}"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
def some_action(data)
value_from_client = data['value_0']
ActionCable.server.broadcast\
"#{BASE_CHANNEL}#{data['some_key']}",
data
end
end
This will create a new custom-named channel based on whatever we want to send in params from the client. some_action is the method that will be called by the clients to send a message. In this case we just rebroadcast the message to all clients.
To make a call from the client to the server, go to the .js or .coffee file for the earlier generated channel (should be in javascripts/channels). We can call back to the server:
jQuery(document).on 'turbolinks:load', ->
session_slug = $('#session_slug').val()
player_name = $('#player_id').val()
$stats =$('#statistics')
App.estimation_session = App.cable.subscriptions.create {
channel: "CoolChannel",
},
connected: ->
disconnected: ->
# Called when the subscription has been terminated by the server
received: (data) ->
value_from_server = data.value_0
some_action: (value_0, value_1)->
@perform 'some_action',
value_0: value_0,
value_1: value_1
When deploying to Heroku, we need to Add Redis To Go add-in (or any other Redis add-in such as Heroku Redi. Then update cable.yml to use redis:
adapter: redis
url: YOUR_URL
To find Redis connection URL, run:
heroku config | grep redis
Also, we need to add redis gem:
gem ‘redis’, ‘~> 3.2’
git push heroku master
Some useful resources:
- https://www.pluralsight.com/guides/ruby-ruby-on-rails/creating-a-chat-using-rails-action-cable
- https://www.sitepoint.com/create-a-chat-app-with-rails-5-actioncable-and-devise/
- http://exponential.io/blog/2015/02/21/install-postgresql-on-mac-os-x-via-brew/
- https://www.digitalocean.com/community/tutorials/how-to-setup-ruby-on-rails-with-postgres
- https://blog.heroku.com/real_time_rails_implementing_websockets_in_rails_5_with_action_cable