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.