背景
Railsでチャット機能の実装はどのようにするのかなと気になって調べてみると「Action Cable」というものを使うと簡単に実装できるとあったので簡単ではあるが、備忘録として自分なりに概要を書こうと思った。
##Action Cableとは
WebSocketとRailsのその他の部分をシームレスに統合するためのもの。Action Cable が導入されたことで、Rails アプリケーションの効率の良さとスケーラビリティを損なわずに、通常のRailsアプリケーションと同じスタイル・方法でリアルタイム機能をRubyで記述できます。
##実装手順
1.speakというアクションをもつ、ChatRoomチャンネルを作成する
※channels/chat_room.coffeeファイルはいらないので削除する
$ rails g channel chat_room speak
2.app/assets/javascripts/chat.coffeeをchat.jsにrenameし以下を記述する。
if(/chat/.test(window.location.pathname)) {
var path = window.location.pathname.split('/');
var room_id = path[path.length - 1];
App.chat_room = App.cable.subscriptions.create({ channel: "ChatRoomChannel", room_id: room_id }, {
connected: function() {},
disconnected: function() {},
received: function(data) {
$('.messages').append(data['content']);
},
speak: function(message) {
return this.perform('speak', {
message: message,
room_id: room_id,
user_id: $('meta[name="current_user_id"]').attr('content')
});
}
});
$(document).on('keypress', '[data-behavior~=room_speaker]', function(event) {
if (event.keyCode === 13) {
App.chat_room.speak(event.target.value);
event.target.value = '';
return event.preventDefault();
}
});
}
3.書き込んだ内容をrenderするために、app/views/chat_messages/_chat_message.html.erb を追加します。
<div class="message">
<span class="message__name">
<%= chat_message.user.name %>:
</span>
<% if chat_message.user.id == current_user.id %>
<div class="message__text message_current_user">
<div><%= chat_message.message %></div>
</div>
<% else %>
<div class="message__text">
<div><%= chat_message.message %></div>
</div>
<% end %>
</div>
4.chat_room_channel.rbを以下のように編集する。
フロントから送信されたデータを受け取っているのでそれを加工して送信して、先ほど作ったテンプレートを使ってレンダリングするように実装する。
class ChatRoomChannel < ApplicationCable::Channel
def subscribed
stream_for "chat_room_#{params[:room_id]}"
end
def unsubscribed
# Any clkanup needed when channel is unsubscribed
end
def speak(data)
message = ChatMessage.create(chat_room_id: data['room_id'], user_id: data['user_id'], message: data['message'])
ChatRoomChannel.broadcast_to "chat_room_#{data['room_id']}", content: render_message(message)
end
private
def render_message(message)
ApplicationController.renderer.render(partial: 'chat_messages/chat_message', locals: { chat_message: message, current_user: message.user })
end
end
5.current_user_idはapp/views/layouts/application.html.erbのmetaタグとして設定してそれを読み出すことで取得できるようにする。
<head>
(中略)
<meta name="current_user_id" content="<%= current_user.id if current_user %>">
(中略)
</head>
※これで完成なのだが自分の場合はここでターボリンクスが悪さをしてエラーが発生したので同じエラーが起きた場合はターボリンクスを無効化してみてください。