Rails4系でのActionCableの使い方

リアルタイムなチャットアプリを実装しながら、ActionCableの基本的な使い方をみていきたいなと思います。

作ったサンプルアプリは、以下に置いております。

https://github.com/totzyuta/chat-actioncable



以下の記事を参考にさせていただきました。


* https://gorails.com/episodes/rails-5-actioncable-websockets

ActionCableの使い方の基礎基礎部分。よいです。


* http://nithinbekal.com/posts/rails-action-cable/

Rails 4系でのActionCableを使う初期の実装部分。こちらも非常によいです。





## 環境

環境はこんな感じです。

```
$ bundle exec rails -v 
Rails 4.2.5
$ cat .ruby-version
2.2.3
```

ActionCable自体のバージョンは2015/12/18現在の最新版で0.0.3です。ActionCable自体はalpha版で、これからどんどん仕様も変わっていくと思われるので、バージョンにご注意ください。。


## Railsアプリのひながた


まずはrailsアプリを作成します。

```
$ rails new chat-actioncable-react
```

Messageモデル関連のリソースを一式作成しておきます。Model使いませんが一応。

```
$ bin/rails g model message
$ bin/rails g controller messages index
```

ルーティングもよしなに。

```ruby
Rails.application.routes.draw do
  root "messages#index"
  resources :messages, only: %i(index create)
end  
```



## gems

Gemfileを更新します。Rails4系でActionCableを使うには、以下のようにします。

```ruby
# Gemfile
source 'https://rubygems.org'

gem 'rails', '4.2.3'
gem 'actioncable', github: 'rails/actioncable'
gem 'puma'
# ...
```

また、Unicornのような単一スレッドで動くWebサーバーを使う場合には別にマルチスレッドで動くPumaとかThinを使う必要があります。Websocketのコネクション自体のサーバーは独立して動かします。

もともとマルチスレッドなアプリケーションサーバーで動かすなら、stand aloneな別アプリケーションサーバーを用意しない方法も実装されてるみたいですね。websocket-railsは、こちらの方法がdefaultになっていたような。

https://github.com/rails/actioncable#running-the-cable-server




## ActionCable初期設定


ActionCableは、RedisのPub/Subを使って処理を行っています。なのでRedisの設定ファイルを書いておく必要があります。Redisはそのまま標準ポートで、こんな感じで。


```yaml
$ 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用設定ファイルを書いておきます。

```ruby
# cable/config.ru
require ::File.expand_path('../../config/environment',  __FILE__)
Rails.application.eager_load!

require 'action_cable/process/logging'

run ActionCable.server
```

そして、アプリケーションサーバーを立ち上げるためのbinコマンドも用意しておきます。今回はPumaを使います。


```ruby
# /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`クラスを継承します。


```ruby
# 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`クラスを定義します。

```ruby
# 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`をこんな感じに。

```html
<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


サーバーサイド側でのチャンネルを作成します。

```ruby
class MessagesChannel < ApplicationCable::Channel
  def subscribed
    stream_from "messages"
  end
end
```



## MessageのBroadcasting

では、Messages#createがリクエストされたときにbroadcastするようにcreateメソッドを作っておきます。

```ruby
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`を作成します。


```coffee
#= require cable
#= require_self
#= require_tree

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

ここで`channels`ディレクトリ以下の他のファイルの読み込みも行うっぽいです。AssetPipelineのマニフェストと書き方は一緒なので馴染みがある。(けどマジコメ的なのは難しいので個人的に好きではない)

次にこのディレクトリの中に`messages.coffee`を作ってMessagesChannel用のクラスを作成します。

```coffee
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
```

![example.gif](https://qiita-image-store.s3.amazonaws.com/0/37676/fc5b2597-8c34-809d-e36b-a4f842a0d989.gif)




動きましたーー気持ちい！！

GitHubに作ったものを置いているのでよかったら参考にしてやってください...！

https://github.com/totzyuta/chat-actioncable