Swiftorial Logo
Home
Swift Lessons
Matchups
CodeSnaps
Tutorials
Career
Resources

Ruby on Rails - WebSockets in Rails

Introduction

WebSockets provide a way to open a persistent connection between the client and server, allowing for real-time communication. Rails integrates WebSockets through Action Cable, making it easy to add real-time features to your application. This guide will cover how to implement WebSockets in Rails with Action Cable.

Key Points:

  • WebSockets enable real-time communication between the client and server.
  • Action Cable is Rails' built-in framework for managing WebSockets.
  • This guide covers how to set up and use Action Cable to implement WebSockets in Rails.

Setting Up Action Cable

To use Action Cable, you need to enable it in your Rails application. Here is how to set it up:

# config/cable.yml
development:
  adapter: async

test:
  adapter: async

production:
  adapter: redis
  url: redis://localhost:6379/1

# config/routes.rb
Rails.application.routes.draw do
  mount ActionCable.server => '/cable'
  # Other routes...
end

# app/assets/javascripts/cable.js
//= require action_cable
//= require_self
//= require_tree ./channels

(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);
                

In this example, the configuration for Action Cable is set in cable.yml, and the server is mounted in the routes file. The JavaScript setup for Action Cable is included in cable.js.

Creating a Channel

Channels in Action Cable are similar to controllers in Rails. They handle the WebSocket connections and perform actions based on client requests. Here is an example of creating a channel:

# Generate a new channel named ChatChannel
rails generate channel Chat

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params['room']}_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def send_message(data)
    ActionCable.server.broadcast "chat_#{params['room']}_channel", message: data['message']
  end
end

# app/assets/javascripts/channels/chat.coffee
App.chat = App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
  connected: ->
    # Called when the subscription is ready for use on the server

  disconnected: ->
    # Called when the subscription has been terminated by the server

  received: (data) ->
    # Called when there's incoming data on the WebSocket for this channel
    $('#messages').append "<p>#{data.message}</p>"

  send_message: (message) ->
    @perform 'send_message', message: message
                

In this example, a new channel named ChatChannel is created. It streams from a room-specific channel and defines a method to broadcast messages. The corresponding JavaScript sets up the subscription and handles incoming messages.

Broadcasting Messages

To broadcast messages to a channel, you can use the ActionCable.server.broadcast method. Here is an example:

# app/controllers/messages_controller.rb
class MessagesController < ApplicationController
  def create
    @message = Message.create!(message_params)
    ActionCable.server.broadcast "chat_#{params['room']}_channel", message: @message.content
  end

  private

  def message_params
    params.require(:message).permit(:content)
  end
end
                

In this example, a new message is created and broadcast to the chat channel.

Securing Channels

You can secure your channels by authorizing subscriptions. Here is an example:

# app/channels/application_cable/connection.rb
module ApplicationCable
  class Connection < ActionCable::Connection::Base
    identified_by :current_user

    def connect
      self.current_user = find_verified_user
      reject_unauthorized_connection unless current_user
    end

    private

    def find_verified_user
      if verified_user = User.find_by(id: cookies.signed[:user_id])
        verified_user
      end
    end
  end
end
                

In this example, the connection is authorized by checking if the user is verified based on a signed cookie.

Handling Disconnections

You can handle disconnections in your channel by defining the unsubscribed method. Here is an example:

# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
  def subscribed
    stream_from "chat_#{params['room']}_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end
                

In this example, the unsubscribed method is defined to perform any necessary cleanup when a user disconnects from the channel.

Deploying with Action Cable

When deploying your Rails application with Action Cable, ensure that your WebSocket server is properly configured. Here is an example for deploying with Puma:

# config/puma.rb
# Change to match your CPU core count
workers 2

# Min and Max threads per worker
threads 1, 6

app_dir = File.expand_path("../..", __FILE__)
shared_dir = "#{app_dir}/shared"

# Default to production
rails_env = ENV['RAILS_ENV'] || "production"
environment rails_env

# Set up socket location
bind "unix://#{shared_dir}/sockets/puma.sock"

# Logging
stdout_redirect "#{shared_dir}/log/puma.stdout.log", "#{shared_dir}/log/puma.stderr.log", true

# Set master PID and state locations
pidfile "#{shared_dir}/pids/puma.pid"
state_path "#{shared_dir}/pids/puma.state"
activate_control_app

on_worker_boot do
  require "active_record"
  ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
  ActiveRecord::Base.establish_connection(YAML.load_file("#{app_dir}/config/database.yml")[rails_env])
end
                

In this example, Puma is configured to use UNIX sockets and manage multiple workers for handling WebSocket connections efficiently.

Conclusion

WebSockets in Rails, powered by Action Cable, provide a powerful way to add real-time features to your application. By understanding how to set up, create channels, broadcast messages, secure channels, handle disconnections, and deploy with Action Cable, you can build robust real-time applications with Rails.