Блог Ruby-разработчика

Простой чат с помощью ActionCable

| Comments

ActionCable, который ожидается в Rails 5, наконец на этой неделе был замержен в мастер ветку Rails. Давайте создадим простой чат на его основе.

Если вы не знаете, что такое ActionCable, то это библиотека, которая позволяет интегрировать WebSocket-ы c вашим Rails-приложением Т.е мы имеем необходимый инструмент как на стороне клиента, так и на стороне сервера.

Итак, нам понадобится:

  • Установленный Redis (для пользователей Ubuntu достаточно одной команды sudo apt-get install redis-server)
  • Rails 4.2 +

Создаем костяк проекта

Назовем его chat, пропускаем установку gems, с помощью опции -B

1
rails new chat -B

Теперь о необходимых гемах для работы нашего чата, нам понадобится:

  • Сервер приложения - Puma. Webrick тут не подходит так как ActionCable использует отдельный процесс нашего App сервера, и поэтому нам нужен многопоточный сервер Puma или Thin
  • Шаблонизатор slim (просто привычка работать именно с ним)
  • ActionCable

Ваш Gemfile должен выглядеть примерно вот так:

Gemfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
source 'https://rubygems.org'
gem 'rails', '4.2.5'
gem 'sqlite3'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc

gem 'actioncable', github: 'rails/actioncable'
gem 'slim-rails'
gem 'puma'

group :development, :test do
  gem 'byebug'
end

group :development do
  gem 'web-console', '~> 2.0'
  gem 'spring'
end

Выполняем установку gems, bundle install

Структура проекта

В нашем проекте будут контроллеры:

  • MessagesController - будет отвечать за вывод сообщений и их создание
  • SessionsController - в нем мы создадим простую cookie-аутентификацию для пользователя

При создании контроллеров будем использовать дополнительные опции, –no-helper и –no-assets, чтобы не “плодить” лишние файлы.

Контроллеры и маршруты

Создаем контроллер Sessions

1
rails g controller Sessions --no-helper --no-assets

Добавляем create action

app/controllers/sessions_controller.rb
1
2
3
4
5
6
7
8
class SessionsController < ApplicationController

  def create
    cookies.signed[:username] = params[:session][:username]
    redirect_to messages_path
  end

end

Создаем контроллер Messages

1
rails g controller Messages --no-helper --no-assets

Action для index не обязателен, мы его опустим, создадим action create

app/controllers/messages_controller.rb
1
2
3
4
5
6
7
class MessagesController < ApplicationController

  def create
    head :ok
  end

end

В нем мы возвращаем заголовок :ok для ajax запроса. Теперь пропишем маршруты для наших контроллеров в routes.rb

app/config/routes.rb
1
2
3
4
5
6
Rails.application.routes.draw do
  resources :messages, only: [:index, :create]
  resources :sessions, only: [:new, :create]

  root 'sessions#new'
end

Корень нашего чата будет вести на логин пользователя

Создаем View

Для начала создадим страничку new для SessionsController, в которой будет простая форма с одним инпутом

app/views/sessions/new.html.slim
1
2
3
4
5
6
= form_for :session, url: sessions_path do |form|
  = form.label :username, 'Ваш никнейм'
  br
  = form.text_field :username
  br
  = form.submit 'Войти в чат'

Теперь создадим страницу на которой будут выводиться все сообщения и в ней будет находиться форма с созданием нового сообщения

app/views/messages/index.html.slim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
p
  | Вы вошли как
  '
  b #{cookies.signed[:username]}

#messages
br

= form_for :message, url: messages_path, remote: true, id: 'messages-form' do |form|
  = form.label :body, 'Введите сообщение:'
  br
  = form.text_field :body
  br
  = form.submit 'Отправить сообщение'

Здесь мы выведем имя залогинившегося пользователя, и создадим контейнер для сообщений #messages

Настройка ActionCable(backend)

Для работы с ActionCable необходимо создать 2 класса Connection и Channel Создадим их в папке app/channels/application_cable Прописывать channels в autoloads не нужно, Rails подгрузит их по умолчанию

app/channels/application_cable/connection.rb
1
2
3
4
module ApplicationCable
  class Connection < ActionCable::Connection::Base
  end
end
app/channels/application_cable/channel.rb
1
2
3
4
module ApplicationCable
  class Channel < ActionCable::Channel::Base
  end
end

Настройка Redis

ActionCable использует Redis, добавим конфигурационный файл в config/redis/cable.yml. Настройки довольно стандартные для Redis.

config/redis/cable.yml
1
2
3
4
5
6
7
8
9
default: &default
  url: redis://localhost:6379
  host: localhost
  port: 6379
  timeout: 1
  inline: true

development: *default
test: *default

Так как ActionCable использует отдельный процесс, то создадим rackup файл с конфигурацией cable/config.ru

cable/config.ru
1
2
3
4
5
6
require ::File.expand_path('../../config/environment',  __FILE__)
Rails.application.eager_load!

require 'action_cable/process/logging'

run ActionCable.server

Для удобства запуска нашего ActionCable сервера, давайте добавим sh скрипт в папку bin и назовем его cable

bin/cable
1
2
# /bin/bash
bundle exec puma -p 28080 cable/config.ru

Не забываем поставить этому файлу права на исполнение chmod +x bin/cable

Теперь создадим MessagesChannel, ответственный за подписку на стрим

app/channels/messages_channel.rb
1
2
3
4
5
class MessagesChannel < ApplicationCable::Channel
  def subscribed
    stream_from 'messages'
  end
end

Далее изменим наш action create в MessagesController, добавим функционал по отсылке сообщений

app/controllers/messages_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MessagesController < ApplicationController
  def create
    ActionCable.server.broadcast 'messages',
                                 message: message_params[:body],
                                 username: cookies.signed[:username]
    head :ok
  end

  private

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

Настройка client-side

Создадим папку channels в app/assets/javascripts. Сначала необходимо создать соединение с нашим ActionCable сервером, создаем index.coffee

app/assets/javascripts/channels/index.coffee
1
2
3
4
5
6
#= require cable
#= require_self
#= require_tree .

@App = {}
App.cable = Cable.createConsumer 'ws://127.0.0.1:28080'

Теперь подпишемся на MessagesChannel, создаем messages.coffee

app/assets/javascripts/channels/messages.coffee
1
2
3
4
5
6
App.messages = App.cable.subscriptions.create 'MessagesChannel',
  received: (data) ->
    $('#messages').append @renderMessage(data)

  renderMessage: (data) ->
    "<p><b>[#{data.username}]:</b> #{data.message}</p>"

Здесь в функции received мы получаем данные и вставляем отрендеренные сообщения в #messages контейнер

Теперь добавим наш js(//= require channels) в application.js

app/assets/javascripts/application.js
1
2
3
4
5
//= require jquery
//= require jquery_ujs
//= require turbolinks
//= require channels
//= require_tree .

Наш чат готов!

Запускаем наше rails приложение rails s, потом запускаем ./bin/cable. Открываем либо два браузера, либо запускаем вкладку в режиме инкогнито, переходим на http://localhost:3000 и проверяем работу чата.

Comments