Action Cableでできることと実装方法

※2022年から技術系の記事は[個人ブログ](https://yamaday0u.net/category/programmer/)に投稿しております。ぜひこちらもご覧ください→[yamaday0u Blog](https://yamaday0u.net/category/programmer/)

Action Cableは、Railsのアプリケーションと同様の記述で、WebSocket通信という双方向の通信によるリアルタイム更新機能を実装できるフレームワークで、Rails5から実装されました。

Action Cableを利用することで、たとえばリアルタイムで更新されるチャット機能を実装することができます。

## チャネルの作成

### channel
リアルタイム更新機能を実現するサーバー側の仕組みでデータの経路を設定したり、送られてきたデータを画面上に表示させたりします。

###チャネルを作成するコマンド

```zsh:ターミナル
 rails g channel message #チャネル名
# ↓実行結果↓
Running via Spring preloader in process *****
      invoke  test_unit
      create  test/channels/message_channel_test.rb
      create  app/channels/message_channel.rb
   identical  app/javascript/channels/index.js
   identical  app/javascript/channels/consumer.js
      create  app/javascript/channels/message_channel.js
```

生成されるファイルのうち以下の2つが重要です。
`app/channels/message_channel.rb ` → クライアントとサーバーを結びつけるためのファイル
`app/javascript/channels/message_channel.js` → サーバーから送られてきたデータをクライアントに描画するためのファイル


## クライアントとサーバーを結びつける
### stream_fromメソッド
サーバーとクライアントを関連づけるメソッドでAction Cableにあらかじめ用意されています。

```app/controller/messages_controller.rb
class MessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "message_channel" #記述箇所
  end

  def unsubscribed
    # Any cleanup needed when channel is unsubscribed
  end
end
```

### broadcast
stream_fromメソッドで関連付けられるデータの経路で、サーバーから送られるデータの経路です。broadcastを介してデータをクライアントに送信します。

`broadcast`を使ってコントローラーへ以下のように記述します。

```app/controller/messages_controller.rb
class MessagesController < ApplicationController
  def new
    @messages = Message.all
    @message = Message.new
  end

  def create
    @message = Message.new(text: params[:message][:text])
    if @message.save
      ActionCable.server.broadcast 'message_channel', {content: @message}
    end
  end
end
```
`broadcast`という経路を通して、`message_channel`に向けて`@message`を`content`に入れてデータを送信しています。

### JavaScriptへの記述
サーバーから送信されたデータは`data`の中に`content`という名称で入っているので、`data.content.text`で使用できます。

```js:app/javascript/channels/message_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("MessageChannel", {
  received(data) {
    // 以下で使用
    const html = `<p>${data.content.text}</p>`;
    const messages = document.getElementById('messages');
    const newMessage = document.getElementById('message_text');
    messages.insertAdjacentHTML('afterbegin', html);
    newMessage.value='';
  }
});
```

ちなみにビューはこんな感じです。

```erb:app/views/messages/new.html.erb
<h3>mini-talk-app</h3>
<%= form_with model: @message do |f| %>
  <%= f.text_field :text %>
  <%= f.submit '送信' %>
<% end %>
<div id="messages">
  <% @messages.reverse_each.each do |message| %>
    <p><%= message.id %></p>
    <p><%= message.text %></p>
  <% end %>
</div>
```

### content以外もクライアントに送信できる
`app/controller/messages_controller.rb`では保存したメッセージの値を`content`に入れて送信していましたが、設定すれば他の値も送ることができます。

ぼくが今開発しているアプリでは、こんな感じで情報を追加してクライアント側に送信しています。

```app/controllers/chats_controller.rb
class ChatsController < ApplicationController  def create
    @chat = Chat.new(chat_params)
    if @chat.save
      # userとtimeにそれぞれ@userと@timeを入れている
      @user = current_user
      @time = @chat.created_at.strftime("%Y-%m-%-d %-H:%-M")
      ActionCable.server.broadcast 'chat_channel', content: @chat, user: @user, time: @time
    end
  end

  private
  def chat_params
    params.require(:chat).permit(:text).merge(
      user_id: current_user.id,
      group_id: params[:group_id]
    )
  end
end
```
クライアント（JavaScript）側ではこのようにデータを利用しています。JavaScript側で投稿日時の表示形式を変更する方法が分からなかったので、事前にサーバー（Ruby）側で表示形式を変更してから送信しています。

```app/javascript/channels/chat_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("ChatChannel", {
  received(data) {
    // 送信されたデータを以下で使用
    const html = `<div class="each-chat">
                    <p class="chat-time">${data.time}</p>
                    <p class="chat-text">${data.content.text}</p>
                    <p class="chat-user">${data.user.name}</p>
                  </div>`;
    const chats = document.getElementById("chats");
    chats.insertAdjacentHTML("afterbegin", html);
    const newChat = document.getElementById("chat_text");
    newChat.value="";
  }
});

```


## 参考資料
[【Rails6.0】ActionCableを使用したライブチャットアプリを実装する手順を解説](https://techtechmedia.com/action-cable-rails6/)