Rails4系でのActionCableの使い方
Rails4でリアルタイムなチャットアプリを実装しながら、ActionCableの基本的な使い方をみていきたいなと思います。
完成形こんな感じ。
作ったサンプルアプリは、以下に置いております。
以下の記事を参考にさせていただきました。
ActionCableの使い方の基礎基礎部分。よいです。
Rails 4系でのActionCableを使う初期の実装部分。こちらも非常によいです。
環境
環境はこんな感じです。
$ bundle exec rails -v Rails 4.2.5 $ cat .ruby-version 2.2.3
Railsアプリのひながた
まずはrailsアプリを作成します。
$ rails new chat-actioncable-react
Messageモデル関連のリソースを一式作成しておきます。Model使いませんが一応。
$ bin/rails g model message $ bin/rails g controller messages index
ルーティングもよしなに。
Rails.application.routes.draw do root "messages#index" resources :messages, only: %i(index create) end
gems
Gemfileを更新します。Rails4系でActionCableを使うには、以下のようにします。
# Gemfile source 'https://rubygems.org' gem 'rails', '4.2.3' gem 'actioncable', github: 'rails/actioncable' gem 'puma' # ...
Unicornのような単一スレッドで動くWebサーバーを使う場合には別にマルチスレッドで動くPumaとかThinを使う必要があります。Websocketのコネクション自体のサーバーは独立している感じですね。
もともとマルチスレッドなアプリケーションサーバーで動かすなら、stand aloneな別アプリケーションサーバーを用意しない方法も実装されてるみたいです。
https://github.com/rails/actioncable#running-the-cable-server
ActionCable初期設定
ActionCableは、RedisのPub/Subを使って処理を行っています。なのでRedisの設定ファイルを書いておく必要があります。Redisはそのまま標準ポートで、こんな感じで。
$ cat config/redis/cable.yml production: &production :url: redis://localhost:6379 :host: localhost :port: 6379 :timeout: 1 local: &local :url: redis://localhost:6379 :host: localhost :port: 6379 :timeout: 1 :inline: true development: *local test: *local
そしたら次に、websocketのプロセスのためのrack用設定ファイルを書いておきます。
# cable/config.ru require ::File.expand_path('../../config/environment', __FILE__) Rails.application.eager_load! require 'action_cable/process/logging' run ActionCable.server
そして、アプリケーションサーバーを立ち上げるためのbinコマンドも用意しておきます。今回はPumaを使います。
# /bin/bash bundle exec puma -p 28080 cable/config.ru
パーミッション変更して実行権限つけときます。
$ chmod 755 bin/cable
おもむろに$ bin/cable
でPumaを立ち上げてちゃんと走るか確かめてみてください。
ActionCableのクラス作成
最初にActionCableの設定をしときます。現状generatorはないっぽいので、ひとつひとつ手作業で追加していきます。
こちらで設定例がのってます。
https://github.com/rails/actioncable#examples
ActionCableを使うにあたって、ApplicationCable::Connection
クラスと、applicationCable::Channel
クラスの二つのクラスを定義する必要があるみたいです。
ApplicationCable関連のクラスを定義するためのディレクトリを作成しておきます。
$ mkdir -p app/channels/application_cable
まずは、ApplicationCable::Connection
クラスを定義します。
The first thing you must do is define your ApplicationCable::Connection class in Ruby. This is the place where you authorize the incoming connection, and proceed to establish it if all is well. Here's the simplest example starting with the server-side connection class:
このクラスは、接続のauthorizeや接続の確立の処理を行うクラスですね。ApplicationCable::Connection
はActionCable::Connection::Base
クラスを継承します。
# app/channels/application_cable/connection.rb module ApplicationCable class Connection < ActionCable::Connection::Base end end
ActionCable::Connection::Base
クラスはこちらに定義されています。
https://github.com/rails/actioncable/blob/master/lib/action_cable/connection/base.rb
このクラスに接続確立時にフックできそうなインスタンスメソッドとかも定義されてるので、把握しておくと機能追加だけでなくデバッグ時などにもいろいろ便利そうです。
あとコメントのドキュメントが結構書かれてて読むと理解が捗ります。読んでるとRailsのソースって半分以上コメントなんじゃないかと思うときある。
ということで次に、ApplicationCable::Channel
クラスを定義します。
# app/channels/application_cable/channel.rb module ApplicationCable class Channel < ActionCable::Channel::Base end end
定義場所はこちら。
https://github.com/rails/actioncable/blob/master/lib/action_cable/channel/base.rb
Views
ということで次に、Viewを編集しておきます。
今回はMessages#index
でメッセージの一覧を表示するようにします。app/views/index.html.erb
をこんな感じに。
<div id='messages'></div> <br/><br/> <%= form_for :message, url: messages_path, remote: true, id: 'messages-form' do |f| %> <%= f.label :username, 'Username:' %><br/> <%= f.text_field :username %><br/> <%= f.label :body, 'Message:' %><br/> <%= f.text_field :body %><br/> <%= f.submit 'Send message' %> <% end %>
id='messages'
のcontentにajaxで送信されたmessageをどんどんappendしていくような感じですね。とりあえず軽量に作りたいのでこんな感じで笑
ApplicationCable::Channel
サーバーサイド側でのチャンネルを作成します。
class MessagesChannel < ApplicationCable::Channel def subscribed stream_from "messages" end end
MessageのBroadcasting
では、Messages#createがリクエストされたときにbroadcastするようにcreateメソッドを作っておきます。
class MessagesController < ApplicationController def index end def create ActionCable.server.broadcast "messages", body: params[:message][:body], username: params[:message][:username] head :ok end end
第一引数にbroadcastする対象を、clientサイドに渡したいdataを第二引数にハッシュで渡します。
ActionCable::Server::Broadcastingは以下に定義されています。例によってコメント読むと理解が捗ります。
https://github.com/rails/actioncable/blob/master/lib/action_cable/server/broadcasting.rb
クライアントサイド
今度はクライアントサイドを実装します。
ActionCable関連のクラスはapp/assets/javascripts/channels
ディレクトリ以下で作っていきます。
$ mkdir app/assets/javascripts/channels
まずは、接続を確立するための処理をするindex.coffee
を作成します。
#= require cable #= require_self #= require_tree @App = {} App.cable = Cable.createConsumer "ws://127.0.0.1:28080"
ここでchannels
ディレクトリ以下の他のファイルの読み込みも行うっぽいです。AssetPipelineのマニフェストと書き方は一緒なので馴染みがある。(けどマジコメ的なのは難しいので個人的に好きではない)
次にこのディレクトリの中にmessages.coffee
を作ってMessagesChannel用のクラスを作成します。
App.messages = App.cable.subscriptions.create "MessagesChannel", received: (data) -> $("#messages").append @renderMessage(data) renderMessage: (data) -> "<p><b>[#{data.username}]:</b> #{data.body}"
MessagesChannelでreceivedが呼ばれると、#messagesのcontentをappendします。
receivedメッセージは、指定したチャンネルでWebsocketを伝ってメッセージが送られてきたときにcallされます。そのとき受け取ったデータはJSON形式でエンコーディングされていて、data.username
みたいにアクセスすることができます。
そしたら最後に忘れないように、application.jsのマニフェストに、これらのファイルをincludeするように書いておきましょう。
//= require channels
立ち上げてみる
ということで立ち上げてみます。
$ redis-server $ bin/cable $ bin/rails s
動きましたーー気持ちい!!
GitHubに作ったものを置いているのでよかったら参考にしてやってください...!