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