Developing a Simple app with Action Cable and Devise on Heroku

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:

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.