Action Cableでリアルタイムチャットアプリの作成方法 （Rails 5.1.4にて）（その１）　herokuで動かす！

#概略
Action Cableを使ったリアルタイムチャットアプリの作り方を解説したサイトは複数あるが、どれもbeta版やrc版を使って解説したものである。
そこで、Rails5.1.4使用してAction Cableを使用するためにいろいろ調べたものをまとめてメモとしてこの記事を書いている。
beta版と正式版ではいろいろ仕様が異なる。（redisが不要になったなど）
調べながら作業をすると、頭が混乱するので、記事に調べたものをまとめ、この記事を見ながら作業するのが目的。

まず、Action Cableを使った単純なチャットアプリを作り、その後、特定のユーザーへのダイレクトメッセージ機能を作る（予定）
その１では、Action Cableのサンプルを作る。（おまけでherokuに公開）
その２では、ダイレクトメッセージを実装（予定）

#環境
Ruby2.2.4
Rails 5.1.4

#参考にしたサイト
[TICLECODE Ruby on Rails5.0.0.rc1のAction Cableを使ってチャットアプリを作ろう！#Rails #coedorb](https://ticklecode.com/rails-5-0-0-rc1-chat-action-cable/)

[Qiita Rails 5 + ActionCableで作る！シンプルなチャットアプリ（DHH氏のデモ動画より）](https://qiita.com/jnchito/items/aec75fab42804287d71b)

[CodeZine Rails 5の目玉機能「Action Cable」で双方向通信を実装 ](https://codezine.jp/article/detail/10153)

以上の3つ

#手順
##1. アプリの作成
Action Cableのテスト用アプリなのでac_testとします。

```
$rails new ac_test
```

##2. コントローラー作成
roomsコントローラーを作成し、showアクションとviewも同時に作成する。

```
$rails g controller rooms show
```

サーバーを起動し、http://localhost:3000/rooms/show 
にアクセスするとページが表示されるのを確認。

```
$rails s
```
##3. messageモデルを作成する。
messageモデルを作成し、同時にtextデータ型のcontentカラムも作成する。
その後、マイグレーションする。

```
$rails g model message content:text
$rails db:migrate
```


##4. controller・viewの作成・編集
roomsのコントローラーのshowアクションを上記のように編集（追記）し、@messages変数に全ての投稿を取得、代入する。

```ruby:app/controllers/rooms_controller.rb
  def show
    @messages = Message.all
  end
```

viewファイルを以下のように編集し、controllerで代入した@messagesを表示する。

```html.erb:app/views/rooms/show
<h1>Chat room</h1>

<div id="messages">
  <%= render @messages %>
</div>
```
_message.html.erbというファイルを以下にように作成・編集する。
このファイルは投稿を一件だけ取得するためのテンプレート

```html.erb:app/views/messages/_message.html.erb
<div class="message">
  <p><%= message.content %></p>
</div>
```


Memo：ファイル名の先頭にアンダーバーをつけるのは、パーシャルという機能を使用するため？

##5. 投稿データを登録してテスト
コンソールを起動

```
$rails c
```

MessageテーブルのcontentカラムにHelloを登録。
手順2と同じ手順でサーバー起動してアクセスすると、投稿したデータが表示されている（はず）。

```
Message.create! content: "Hello"
```


##6. Roomチャンネルの作成（未知の世界へ）
speakというアクションを持つRoomチャンネルを作成。
すると、
`app/channels/room_channel.rb`（サーバーサイド用）
`app/assets/javascripts/channels/room.coffee`（クライアントサイド用）
という2つのファイルが生成される。

```
$ rails g channel room speak
```


サーバーを起動して、ブラウザーのコンソールから

`App.room.speak()`を入力して`true`返ってくれば成功。

Memo：beta版ではこの後、routes.rbのファイルの編集が必要であったが、正式版では不要になった。また、cable.coffeeはcable.jsに変更され、編集が不要になった。（戸惑った...）

##7. speakアクションの定義
クライアントサイドのspeakアクションを定義して、そのアクションでサーバーサイドのspeakアクションを呼び、messageをmessageというパラメーターとして渡す。

```ruby:app/assets/javascripts/channels/room.coffee
(省略)

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

```

subscribedアクションの中身をコメントを外し、some_channelをroom_channelに書き換える。
また、サーバーサイドのspeakアクションを定義して、クライアントサイドのspeakアクションから渡されたパラメーターをdataとして受け取り、room_channelにmessageをブロードキャストする。

```ruby:app/channels/room_channel.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    stream_from "room_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak(data)
    ActionCable.server.broadcast 'room_channel', message: data['message']
  end
end
```

データを受け取った時の仮のアクション（アラート表示）を以下のように定義して動作確認を行う。

```app/assets/javascripts/channels/room.coffee
received: (data) ->
    alert data['message']
```
サーバーを起動してコンソールに`App.room.speak("test")`を入力してアラートが表示されれば成功。


Memo：channelsというディレクトリがApp内やJavascript内など複数の場所に同名で存在したり、rooms.coffeeやroom.coffeeなどややこしい名前のファイルが多く、混乱しやすいので注意！！！

##8．フォームを使ったデータ送信
showのviewに入力フォームを追加する。

```erb:app/views/rooms/show.html.erb
<form>
  <label>Say something:</label><br>
  <input type="text" data-behavior="room_speaker">
</form>
```
次に、入力後にエンターキーを押したときの動作を定義

```coffeescript:app/assets/javascripts/channels/room.coffee
$(document).on 'keypress', '[data-behavior~=room_speaker]', (event) ->
  if event.keyCode is 13 # return = send
    App.room.speak event.target.value
    event.target.value = ''
    event.preventDefault()

```

サーバーを起動し、ブラウザーにアクセスして、テキストボックスに文字を入力して、エンターキーをおして、アラートが出れば、成功。

Memo：エンターキーの処理をするのにjQueryを使用するので、jQueryの読み込みが必要。

##9. データの保存とブロードキャストの改善
このままだと、入力とブロードキャストされたものはアラートされるだけで、保存がされない。
まず、speakアクションを書き換える。(ブロードキャスト処理は非同期通信のためにjobに移動する)

```ruby:app/channels/room_channel.rb
  def speak(data)
    Message.create! content: data['message']
  end
```

次に、MessageBroadcastのjobを作成する。

```
$rails g job MessageBroadcast
```
次に作成されたjobファイルを編集し、ブロードキャスト処理を追加する。
最初に作成したテンプレートのmessages/_message.html.erbにmessageを渡して、代入したものを再度messageに代入する。

```app/jobs/message_broadcast_job.rb
def perform(message)
    ActionCable.server.broadcast 'room_channel', message: render_message(message)
  end

  private
    def render_message(message)
      ApplicationController.renderer.render(partial: 'messages/message', locals: { message: message })
    end
end
```

Memo：Active jobは処理をキューに登録することでバックグラウンドで順番に処理を行うためのフレームワーク？（いまいち理解できていない...）
非同期通信をするのに使っている？

次に、messageモデルを編集して、speakのアクションによってmessageテーブルにデータがcreateされた後に、MessageBroadcastのperformアクションを実行するように追記する。そして、speakアクションから送信されたデータをselfを使ってmessageモデルとして渡す。

```app/models/message.rb
class Message < ApplicationRecord
  after_create_commit { MessageBroadcastJob.perform_later self }
end
```

この状態で、ブラウザーにアクセスし、文字をエンターすると、テンプレートに代入されたmessageがアラートされる。

##10．非同期で画面に入力した文字を表示させる。
最後に、ブロードキャストされたmessageをクライアントサイドのrecieveの処理を変更し、viewにappendする。

```app/assets/javascripts/channels/room.coffee
 received: (data) ->
    $('#messages').append data['message']
```

これで、入力したものが更新なしで画面に反映されるようになった。
また、同時にデータベースに登録される。


その１おわり

##おまけ　herokuで公開するための設定（難関）
herokuに公開するのに非常に苦労しました。
beta版まではAction Cableの利用にredisが必要で、正式版ではredisが不要になった。
herokuでredisを使用するためには有料のプランのアドオンを使用しなければならず、今回Action Cableの使用に挑戦したのはredisが不要になったという理由があります。
しかし、情報がない。
localでは動くのに、herokuにあげると動かない。
ログを確認すると、こんなエラーが...

```
Gem::LoadError 
(Specified 'redis' for Action Cable pubsub adapter, but the gem is not loaded. Add `gem 'redis'` to your Gemfile 
```
あら、redisは不要じゃなかったの！！？

さんざん悩んだ結果以下の2点を変更することで解決。
###1. production.rbを編集（これは情報アリ）
config/environment/production.rbに以下を追記（コメントを解除し編集）

```ruby:config/environment/production.rb
 config.action_cable.allowed_request_origins = [ /http:\/\/.*/ ]
```

###2. config/cable.ymlを編集（これに悩んだ）
production:に書かれているものをコメントアウトして、adapter: asyncを追加。

```config/cable.yml
production:
  # adapter: redis
  # url: redis://localhost:6379/1
  # channel_prefix: ac_test_production
  adapter: async
```

これで、無事heroku上で動作しました。

以上

その２では、ダイレクトメッセージの実装をする（予定）

追記
[Action Cableでリアルタイムチャットアプリの作成方法 （Rails 5.1.4にて）（その2）グループ別チャンネルの作成！！](https://qiita.com/Hijiri-K/items/7e3c018c8330f69fc4cf)を公開しました～。