ActionCable + jQueryによるチャット機能の実装

##概要
【ActionCable】+【jQuery】を用いたチャットアプリの作成について、説明します。

##目的
Javascriptやcoffeescriptを用いてチャット機能を実装する記事は多く見受けられたのですが、
jQueryしか分からない私は、jQueryを用いてチャット機能を実装する記事を見つけられず、
jQueryでの実装に苦労しました。。。
同じ境遇の方への技術共有と、自分の勉強のために、jQueryを用いたチャット機能の実装方法を説明します！

<font color="DarkGray">
＊一部jQueryではなく、javasctiptも使用していますが、
　ActionCableを使う上で必要なものになりますので、なんとか理解して頂ければと思います。
</font><font color="DarkGray">
＊プログラミング学習を初めて3ヶ月ちょっとの人間が書いた記事です。
　誤った理解もあるかと思いますので、もし気になる箇所が御座いましたら、ご指摘頂けますと幸いです。
</font>

##この記事のゴール
DM(ダイレクトメッセージ)が出来るチャットアプリの完成が最終的なゴールになりますが、
かなりのボリュームになりますので、この記事では全てのユーザが参加出来る
オープンチャット機能の実装をゴールとして説明します。

##目次
1. 環境
2. 前提
3. 準備
4. ActionCableとは？
5. ActionCableの実装方法
6. ActionCableを用いたチャット機能の実装方法
7. 最後に

##環境
rails (5.2.4.2)
jquery-rails (4.3.5)

##前提
jqueryを用いたDOM操作が出来る方

##ActionCableとは？
ActionCableを説明するために、LINEのようなチャットアプリをイメージして欲しいのですが、
メッセージを投稿した時に、新しいメッセージが非同期で投稿者のviewに反映されるのはもちろんですが、
この時、メッセージを受け取ったユーザの画面にも非同期で新しいメッセージが反映されているかと思います。
この機能を実現するには、WebSocketという技術を使用する必要があります。
このWebSocketをRailsで簡単に使用出来るフレームワークがActionCableになります。
今回は、このActionCableを用いてチャット機能を実装していきます。

##準備

ActionCableを実装する前に、まずは準備をします。

プロジェクトの生成については、下記のコマンドで実施して下さい。
今回はcoffeesctiptではなく、jQueryでjavascriptを操作していきますので、
オプションでcoffeescript使わずにjavascriptを使いますよ、という宣言をします。
この宣言の有無によって、後で生成するファイルの形式が変わります。

```
rails new プロジェクト名 --skip-coffee
```

次に、チャットを表示するviewを作るために、コントローラーを生成します。
viewはRoomsコントローラのshowアクションで表示するようにします。

```
$ rails g controller Rooms show
```

チャットで投稿されたメッセージを保存するために、モデルも生成します。
メッセージの内容はcotentというカラムに保存することにします。

```
$ rails g model Message content:string
```

マイグレーションを実行して、データベースに反映させます。

```
$ rails db:migrate
```

viewにメッセージが表示されるようにコントローラーとviewファイルを編集します。

```rb:Rooms_controller.rb
def show
  @messages = Message.all
end
```

```html.erb:rooms>show.html.erb
<h1>Chat Room</h1>

<ul>
  <% @messages.each do |message| %>
    <li><%= message.content %></li>
  <% end %>
</ul>
```

動作確認をし易いようにルーティングも修正しておきます。

```rb:routes.rb
# get 'rooms/show'
root 'rooms#show'
```

この状態でrails cを用いてcontentをcreateすれば、
contentがviewに表示されることを確認できます。

```
$ rails c
[1] pry(main)> Message.create(content: 'hoge')
```

ここまで実装した状態でhttp://localhost:3000/にアクセスすれば、
下記のような画面が表示されるかと思います。

<img width="373" alt="スクリーンショット 2020-04-24 6.58.15.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/604002/221ba02c-a348-821e-a98a-2ab15dff3fc2.png">
<font color="DarkGray">
＊jQueryのインストール方法については、本記事では割愛しますので、各自で設定をお願いします。
</font>
##ActionCableの実装方法

ActionCableの説明でも述べましたが、チャット機能では、
メッセージを受け取ったユーザの画面にも非同期で新しいメッセージを反映する必要があります。
ActionCableには、フロントとサーバがお互いに監視し合える機能があり、
この機能を使うことでチャット機能が実装できます。

もう少し踏み込んで説明すると、監視し合える機能を使うことで、
「メッセージ投稿者がメッセージを投稿した時に、メッセージをサーバに渡す」
「サーバがメッセージを受け取った時にフロント(全てのユーザ)にメッセージを配信する」
という処理が、できるようになります。

この処理を実施するためにchannelという機能の実装とルーティングの設定が必要になりますので、
下記に従って実装を進めます。

まず、channelという機能を実装します。
Roomはchannelの名前、speakはchannelで使用するメソッドになります。

```
$ rails g channel Room speak
```

上記のコマンドを実行することで、いくつかのファイルが生成されます。

今回は、その中からapp/channels/room_channel.rbと
app/assets/javascripts/channels/room.jsのファイルを使用していきますが、
プロジェクトの生成時に--skip-coffeeオプションを指定されなかった方は、
room.jsファイルが生成されず、room.coffeeが生成されてしまいます。

この場合は、app/assets/javascripts/channels/room.coffeeのファイル名をroom.jsに修正して頂き、
内容を下記のように修正して下さい。javascriptとjQueryが使用出来る状態になります。

```js:room.js
App.room = App.cable.subscriptions.create("RoomChannel", {

  connected: function() {
  },

  disconnected: function() {
  },

  received: function() {
  },

  speak: function() {
  }
});
```
次に、ルーティングを設定します。

```rb:routes.rb
root 'rooms#show'

# 下記を追加する
mount ActionCable.server => '/cable'
```

ここまでで、フロントとサーバがお互いに監視し合える状態になりました。
<font color="DarkGray">(正確にはsubscribedというメソッドの設定も必要ですが、この後説明します)</font>

ここからは、
・チャットメッセージを投稿してサーバに渡す処理
・サーバがメッセージを受け取った時にフロント(全てのユーザ)にメッセージを配信する処理
・非同期でメッセージを表示する処理
　を実装していきます。


まず、viewにフォームを追加してメッセージを投稿できるようにします。

```html.erb:show.html.erb
<h1>Chat Room</h1>

<ul>
  <% @messages.each do |message| %>
    <li><%= message.content %></li>
  <% end %>
</ul>

// 下記を追加する
<input type="text", class="chat-input", autofocus>
<button class="button">送信</button>
```

次に、フォームに入力したメッセージを取得する処理をroom.jsに追加します。

```js:room.js
App.room = App.cable.subscriptions.create("RoomChannel", {

  connected: function() {
  },

  disconnected: function() {
  },

  received: function() {
  },

  speak: function() {
  }
});

// 下記を追加する
$(function(){
  $(".button").on("click",function(){
    var content = $(".chat-input").val();
    App.room.speak(content);
    $(".chat-input").val("")
  });
});
```

各コードについて、説明します。
下記のコードでは、.buttonがクリックされた時に
.chat-input内に入力された内容を変数contentに代入する処理をしています。


```js:room.js
  $(".button").on("click",function(){
    var content = $(".chat-input").val();
  });
```
下記のコードでは、メッセージを代入した変数contentを
room.js内のspeakメソッドに渡しています。

```js:room.js
    App.room.speak(content);
```

下記のコードでは、.chat-input内に入力されていた値を空にしています。

```js:room.js
    $(".chat-input").val("")
```

次に、フォームから取得したメッセージをサーバに送る処理を追加します。
App.room.speak(content)でspeakメソッドに渡した変数contentの値を
messageという形にしてサーバ(room.channel.rbのspeakメソッド)に渡しています。

```js:room.js
App.room = App.cable.subscriptions.create("RoomChannel", {

  connected: function() {
  },

  disconnected: function() {
  },

  received: function() {
  },

  //ここを編集する
  speak: function(content) {
    return this.perform('speak', {content: content});
  }
});

$(function(){
  $(".button").on("click",function(){
    var content = $(".chat-input").val();
    App.room.speak(content);
    $(".chat-input").val("")
  });
});
```

サーバがメッセージを受け取れましたので、
サーバから全てのユーザーにメッセージを配信する処理を追加します。

```rb:room_channel.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    # stream_from "some_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  # ここを編集する
  def speak(data)
    message = Message.new(content: data["content"])
    message.save
    ActionCable.server.broadcast "room_channel", data["content"]
  end
end
```

各コードについて、説明します。
下記のコードでは、引数dataで受け取ったmessageの値をデータベースに保存しています。

```rb:room_channel.rb
  def speak(data)
    message = Message.new(content: data["content"])
    message.save
  end
```

下記のコードでは、引数dataで受け取ったmessageの値をフロント(全てのユーザー)に配信しています。
ActionCable.server.broadcastがサーバーからフロントにデータを配信するメソッドになります。

```rb:room_channel.rb
    ActionCable.server.broadcast "room_channel", data["content"]
```
このメソッドを使用するためには、データをどこに配信するかを
room_channel.rbのsubscribedに宣言する必要がありますので、下記のように、配信先を追加します。
<font color="DarkGray">
配信先として追加する"room_channel"というのは、room.jsに記載されている
App.room = App.cable.subscriptions.create("RoomChannel", {}の
"RoomChannel"のことを指しています。</font>

```rb:room_channel.rb
class RoomChannel < ApplicationCable::Channel
  def subscribed
    # stream_from "some_channel"
    # ここを追加する
    stream_from "room_channel"
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end

  def speak(data)
    message = Message.new(content: data["content"])
    message.save
    ActionCable.server.broadcast "room_channel", data["content"]
  end
end
```

サーバから配信されて、受け取ったデータを非同期で表示するために
room.jsにDOM操作をする処理を追加します。
この処理に伴い、view側もセレクタを追加します。

まずは、viewにメッセージを表示するためにコードを追加します。

```html.erb:show.html.erb
<h1>Chat Room</h1>

<ul>
  <% @messages.each do |message| %>
    <li><%= message.content %></li>
  <% end %>
</ul>

# ここを追加する
<ul id="add"></ul>

<input type="text", class="chat-input", autofocus>
<button class="button">送信</button>
```

次にroom.jsのreceiveメソッドにDOM操作の処理を追加します。
receiveメソッドは、サーバーからデータが配信された時に実行されるメソッドになりますので、
receiveメソッドに処理を追加することで、データがサーバーから配信されたタイミングで
メッセージを表示することができます。

```js:room.js
App.room = App.cable.subscriptions.create("RoomChannel", {

  connected: function() {
    // Called when the subscription is ready for use on the server
  },

  disconnected: function() {
    // Called when the subscription has been terminated by the server
  },

  // ここを編集する
  received: function(data) {
    $('<li>',{
      class: "hoge",
      text: data,
    }).appendTo("#add");

    // Called when there's incoming data on the websocket for this channel
  },

  speak: function(content) {
    return this.perform('speak', {content: content});
  }
});

$(function(){
  $(".button").on("click",function(){
    var content = $(".chat-input").val();
    App.room.speak(content);
    $(".chat-input").val("")
  });
});
```
コードの説明をすると、下記のコードで、
セレクタ#addに対してclassとtextを指定して、liタグを追加しています。
textはliタグに入るtextデータになりますので、
今回は表示したいメッセージのデータdataを指定しています。

```js:room.js
    $('<li>',{
      class: "hoge",
      text: data,
    }).appendTo("#add");
```

ここまで実装できたらhttp://localhost:3000/にアクセスして、
メッセージを投稿して見てください。

メッセージを投稿したユーザーの画面と、
別のユーザーのブラウザ画面でも、メッセージが非同期で表示されていれば実装成功です！

以上で、全てのユーザが参加出来るオープンチャット機能の実装ができました。

##最後に
最後まで、見て頂き有り難う御座いました。

今回は、オープンチャットの実装までとなりましたが、
DMの実装方法についての記事は、こちらに記載しております。
[ActionCable + jQueryによるDM機能の実装](https://qiita.com/kappa_hoge/items/cbf4b0bd8ef976067aef)

また、本記事を作成するにあたっては、下記の動画を参考にさせて頂きました。
本記事では、説明していない部分についても、詳しく説明されていましたので、
詳しく知りたい方は、こちらを参考にして下さい。

[0から手を動かして作るRailsチャットアプリ【チュートリアル】](https://www.youtube.com/watch?v=WCsgcp5dg7M)