Action Cableの習作: Examplesの更新: 4: Action Cableの利用
今回は、Action Cableの利用です。
1. モデルの作成
2. ユーザの認証
3. メッセージとコメントの表示
4. Action Cableの利用
Action Cableの利用
閲覧中のメッセージに別のユーザがコメントしたら、直ちにコメント一覧に追加されるようにします。
Generate Comments channel
コメントチャンネルを生成します。
bin/rails g channel Comments
Add identified_by :current_user
identified_by(*identifiers)
... Note that anything marked as an identifier will automatically create a delegate by the same name on any channel instances created off the connection.
current_userによって接続を識別できるようにします。
クライアントの接続時、クッキーから検証済みのユーザを見つけます。
見つかった場合、検証済みのユーザをcurrent_userに設定します。
見つからない場合、クライアントの接続を許可しません。
app/channels/application_cable/connection.rb
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.encrypted[:user_id])
verified_user
else
reject_unauthorized_connection
end
end
end
end
Add ActionCable.server.disconnect to unauthenticate_user
ユーザの認証解除で、ActionCableを切断します。
app/controllers/concerns/authentication.rb
def unauthenticate_user
ActionCable.server.disconnect(current_user: @current_user)
@current_user = nil
cookies.delete(:user_id)
end
Add stream_from to CommentsChannel#follow
サーバ側の処理です。
コメントチャンネルにfollowメソッドを追加します。
followメソッドは、メッセージのコメントをフォローします。
followメソッドは、引数でmessage_idを受け取ります。
コメントチャンネルにunfollowメソッドを追加します。
unfollowメソッドは、チャンネルに関連づいた全てのストリームを停止します。
上記の処理は、followメソッドの最初でも行います。
app/channels/comments_channel.rb
class CommentsChannel < ApplicationCable::Channel
def follow(data)
stop_all_streams
stream_from "messages:#{data['message_id'].to_i}:comments"
end
def unfollow
stop_all_streams
end
end
Add followCurrentMessage to CommentsChannel#connected
クライアント(コンシューマ)側の処理です。
接続完了時、下記の処理を行います。
・現在のメッセージのフォローを始める。
・ページ変更時の処理をインストールする。
app/javascript/channels/comments_channel.js
connected() {
// FIXME: While we wait for cable subscriptions to always be finalized before sending messages
setTimeout(() => {
this.followCurrentMessage()
this.installPageChangeCallback()
}, 1000)
},
// ...
},
collection() {
return document.querySelector('[data-channel~=comments]')
},
messageId() {
const collection = this.collection()
if (collection) {
return collection.getAttribute('data-message-id')
}
return null
},
followCurrentMessage() {
const messageId = this.messageId()
if (messageId) {
this.perform('follow', { message_id: messageId })
} else {
this.perform('unfollow')
}
},
installPageChangeCallback() {
if (!this.installedPageChangeCallback) {
this.installedPageChangeCallback = true
document.addEventListener('turbolinks:load', event => {
this.followCurrentMessage()
})
}
},
メッセージを表示した時のコンソールの出力例:
[ActionCable] [The Notorious B.I.G.] CommentsChannel#follow({"message_id"=>"1"})
[ActionCable] [The Notorious B.I.G.] CommentsChannel is streaming from messages:1:comments
Add broadcast to Comment#after_commit
コメントが投稿されたら、そのコメントの内容をブロードキャストします。
app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :user
belongs_to :message
after_commit do
comment = CommentsController.render(
partial: 'comments/comment',
locals: { comment: self }
)
ActionCable.server.broadcast(
"messages:#{message_id}:comments",
comment: comment
)
end
end
Add received to CommentsChannel
現在のユーザIDをHTMLのヘッダ部に設定します。
受信したデータ(コメント)が自分のものでなければ、コメントを一覧に追加します。
app/views/layouts/application.html.erb
<head>
...
<meta name="current-user" id="<%= @current_user.try(:id) %>">
</head>
app/javascript/channels/comments_channel.js
received(data) {
const collection = this.collection()
if (!collection) {
return
}
const comment = data.comment
if (this.userIsCurrentUser(comment)) {
return
}
collection.insertAdjacentHTML('beforeend', comment)
},
// ...
userIsCurrentUser(commentHtmlString) {
const comment = this.createElementFromHtmlString(commentHtmlString)
const commentUserId = comment.getAttribute('data-user-id')
return commentUserId === this.currentUserId()
},
createElementFromHtmlString(htmlString) {
const div = document.createElement('div')
div.innerHTML = htmlString
return div.firstElementChild
},
currentUserId() {
return document.getElementsByName('current-user')[0].getAttribute('id')
},
下記の要領で動作を確認します。
別のクッキーとなるようにしてウェブブラウザを2つ開きます。
それぞれ別のユーザでログインします。
それぞれ同じメッセージのページを開きます。
メッセージにコメントすると、他方のウィンドウにコメントが追加されます。
Generate CommentRelayJob
コメントのブロードキャストを非同期処理にするためにジョブを追加します。
bin/rails g job CommentRelay
Move broadcast to CommentRelayJob
コメントのブロードキャスト処理をジョブに移動します。
app/models/comment.rb
class Comment < ApplicationRecord
belongs_to :message
belongs_to :user
after_commit { CommentRelayJob.perform_later(self) }
end
app/jobs/comment_relay_job.rb
class CommentRelayJob < ApplicationJob
queue_as :default
def perform(comment)
html_string = CommentsController.render(
partial: 'comments/comment',
locals: { comment: comment }
)
ActionCable.server.broadcast(
"messages:#{comment.message_id}:comments",
comment: html_string
)
end
end
Action Cableの利用は、以上になります。
この記事が気に入ったらサポートをしてみませんか?