発言にユーザーIDを保存してユーザー名を表示する
最後に、発言と併せてユーザーIDもデータベースに保存するようにします。そのためには、クライアント側とサーバー側(チャネル)でコネクションごとにユーザーを識別できるようにする必要があります。また、データベースに発言を保存する処理とブロードキャスト後にクライアント側で発言を表示する処理に、ユーザー名を追加する必要があります。
コネクションの修正
まず、Action Cableのコネクションを確立する段階でユーザーを識別できるようにするため、リスト10の通りにコネクションを修正します。
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
logger.add_tags 'ActionCable', current_user.name
end
protected
def find_verified_user
if verified_user = User.find_by(id: cookies.signed[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
identified_byメソッドは、WebSocketのコネクションを識別するために提供されており、current_userを識別子として指定しています。
接続するクライアント側のユーザーIDを取得するには、find_verified_userメソッド内のコードの通り、ブラウザのクッキーを用います。ログイン時点で、サーバー側からクッキーをクライアント側に発行した、暗号化されたuser_idをcookies.signed[:user_id]として取得して、ユーザーIDがデータベースに登録されている場合は、current_userにセットされ、これを識別子として用います。ユーザーIDがデータベースに登録されていない場合には、reject_unauthorized_connectionメソッドが呼び出されます。
reject_unauthorized_connectionメソッドは、WebSocket通信を拒否するために提供されているメソッドです。
セッションヘルパーの修正
セッションヘルパーのlog_inメソッドで、暗号化されたユーザーIDをクッキーとして発行するコードをリスト11の通りに追加しましょう。
module SessionsHelper
…(中略)…
def log_in(user)
callback = session[:callback]
reset_session
session[:user_id] = user.id
session[:callback] = callback
cookies.permanent.signed[:user_id] = user.id
end
…(中略)…
コードのpermanentは、発行したクッキーを永続化するためのものです。また、signedを指定することでクッキーに格納されるユーザーIDが暗号化されます。
チャネルの修正
チャネルのspeakメソッドでモデルを生成する箇所に、ユーザーIDをリスト12の通りに追加します。
class ChatMessageChannel < ApplicationCable::Channel
…(中略)…
def speak(data)
ChatMessage.create! user_id: current_user.id, body: data['message']
end
end
モデルの修正
ChatMessage、Userモデルの関連とビュー側で使用するuser_nameメソッドを、リスト13、リスト14の通りに追加します。
class ChatMessage < ApplicationRecord
…(中略)…
belongs_to :user
def user_name
return '名無しさん' if user_id.blank?
user.name
end
end
class User < ApplicationRecord …(中略)… has_many :chat_messages end
ChatMessageモデルのuser_nameメソッドは、これまでのテストでchat_messagesテーブルのuser_idにNullのデータが入っているための措置で、user_idが空だった場合にはユーザー名を「名無しさん」と表示するようにしています。
ジョブの修正
ジョブでブロードキャストする処理に、ユーザー名をリスト15の通り追加します。
class ChatMessageBroadcastJob < ApplicationJob
…(中略)…
def perform(chat_message)
ActionCable.server.broadcast 'chat_message_channel', user_name: chat_message.user_name, message: chat_message.body
end
end
ユーザー名の取得には、ChatMessageモデルに定義したuser_nameメソッドを使っています。
CoffeeScriptの修正
ブロードキャストされた場合、クライアント側で発言を受け取ってhtmlとして表示する箇所を、リスト16の通りに修正します。
…(中略)…
received: (data) ->
$('#chat_messages').append '<div>' + data['user_name'] + ': ' + data['message'] + '</div>'
data['user_name']でブロードキャストされた発言のユーザー名を取得しています。
ビューの修正
最後にブロードキャストした場合と同様、リスト17の通り発言の表示にユーザー名を加えましょう。
<div id='chat_messages'>
<% @chat_messages.each do |chat_message| %>
<div><%= "#{chat_message.user_name}: #{chat_message.body}" %></div>
<% end %>
</div>
動作確認
これで、発言にユーザーIDを保存してユーザー名を表示する修正が完了しました。「http://localhost:3000/chat_messages/index」にアクセスして動作確認をしてみましょう。
図1は、左側がChromeでtest user1としてログインしたもの、右側がSafariでtest user2としてログインして動作確認した結果です。
まとめ
今回は、前回に引き続きAction Cableのサンプルアプリを拡張して、発言をデータベースに保存できるようにし、会員登録機能と関連付けを行いました。
連載最終回の次回は、Rails 5のもう1つの目玉機能であるAPIモードで別のRailsアプリケーションを立ち上げ、今回開発したチャットアプリに機能を追加します。
