※2022年から技術系の記事は個人ブログに投稿しております。ぜひこちらもご覧ください→yamaday0u Blog
Action Cableは、Railsのアプリケーションと同様の記述で、WebSocket通信という双方向の通信によるリアルタイム更新機能を実装できるフレームワークで、Rails5から実装されました。
Action Cableを利用することで、たとえばリアルタイムで更新されるチャット機能を実装することができます。
チャネルの作成
channel
リアルタイム更新機能を実現するサーバー側の仕組みでデータの経路を設定したり、送られてきたデータを画面上に表示させたりします。
###チャネルを作成するコマンド
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にあらかじめ用意されています。
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
を使ってコントローラーへ以下のように記述します。
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
で使用できます。
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='';
}
});
ちなみにビューはこんな感じです。
<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
に入れて送信していましたが、設定すれば他の値も送ることができます。
ぼくが今開発しているアプリでは、こんな感じで情報を追加してクライアント側に送信しています。
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)側で表示形式を変更してから送信しています。
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="";
}
});