#はじめに
今回はActionCableというフレームワークを用いたコメント機能を実装していきます。
#環境
Ruby on Rails '6.0.0'
Ruby '2.6.5'
#前提
記事投稿機能(articleテーブル)
ユーザー管理機能(userテーブル)
上記を実装済みであり、ユーザーは記事を閲覧できる。
#ActionCableとは
通常のrailsのアプリケーションと同様の記述で、即時更新機能を実装できるフレームワーク。メッセージの保存や送信に必要なRubyのコーディングと、保存したメッセージを即時に表示させるJavaScriptのコーディングが必要。
#ajaxによる非同期通信との違いは?
👉通信システムの違い
-Ajax通信
JavaScriptを使って、非同期でサーバーとやり取りをする通信のこと。ブラウザとサーバーの通信とは別に、JavaScriptが通信を行うため、ページ遷移を行わずに情報の更新ができる。しかし、Ajax通信は裏でリクエストを送って、JavaScriptに置き換えているだけのため、リアルタイムなやりとりをするには、常に裏でやりとりをしなければならない。
-WebSocket通信(ActionCable)
サーバー側とユーザー側を常時接続状態にしておき、双方向通信ができるようにする技術。クライアントがリクエストを送信する必要がないため、リアルタイムで情報を更新できる。
やりとりが少ない分、ActionCableを使う方がいいのか?と思いつつ、今回はこちらを採用しました。
では、実装手順を記述していきます。
#①事前準備
簡単なコメント機能を実装していきます。
% rails g controller comments
% rails g model comment
class CreateComments < ActiveRecord::Migration[6.0]
def change
create_table :comments do |t|
t.text :text, null: false
t.references :article, null: false, foreign_key: true
t.references :user, null: false, foreign_key: true
t.timestamps
end
end
end
% rails db:migrate
アソシエーションの設定
class Comment < ApplicationRecord
belongs_to :article
belongs_to :user
validates :text, presence: true
end
class Article < ApplicationRecord
(省略)
has_many :comments, dependent: :destroy
end
class User < ApplicationRecord
(省略)
has_many :comments, dependent: :destroy
end
ルーティングの設定
Rails.application.routes.draw do
(省略)
resources :articles, only: [:index, :show] do
resources :comments, only: [:new, :create]
end
end
コントローラーの設定
class CommentsController < ApplicationController
def new
@article = Article.find(params[:article_id])
@comment = Comment.new
@comments = @article.comments.includes(:user)
end
def create
@comment = Comment.new(comment_params)
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id, article_id: params[:article_id])
end
end
new.html.erbの編集
<div class="container">
<div class="row">
<div class="offset-sm-2 col-sm-8 offset-sm-2">
<% if user_signed_in? %>
<%= form_with model: @comment, url: article_comments_path do |f| %>
<h5 class='form-header-text text-center'>
<i class="fas fa-broom fa-lg my-orange"></i> コメントを投稿する
</h5>
<%= render 'layouts/error_messages', model: f.object %>
<div class="form-group">
<div class='form-text-wrap'>
<label class="form-text" for="text">内容</label>
<span class="badge badge-danger">必須</span>
</div>
<%= f.text_area :text, class:"article-input-default", id:"text", autofocus: true %>
</div>
<div class='article-btn text-center'>
<%= f.submit "コメントする" ,class:"btn btn-outline-danger w-50" %>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
<div class ="container">
<div class="row">
<div class="offset-sm-2 col-sm-8 offset-sm-2">
<h5 class='form-header-text text-center'>
<i class="fas fa-broom fa-lg my-orange"></i> コメント一覧
</h5>
<div id ='comments'>
<% @comments.each do |comment| %>
<p><%= comment.text %></p>
<% end %>
</div>
</div>
</div>
</div>
イメージとして、上部に投稿フォームがあり、下部に投稿されたコメントが一覧で表示されるものです。(多分逆の方が見栄えがいいと思います。笑)
では簡単なコメント投稿機能ができたので、ここからActionCableを導入していきます。
#②チャネル(Channel)の作成
channelとは?
👉即時更新機能を実現するサーバー側の仕組みのこと。データの経路を設定したり、送られてきたデータをクライアントの画面上に表示させる役割。
% rails g channel comment
上記コマンドを実行すると以下のようにファイルが生成されます。
Running via Spring preloader in process 11084
invoke test_unit
create test/channels/comment_channel_test.rb
create app/channels/comment_channel.rb
identical app/javascript/channels/index.js
identical app/javascript/channels/consumer.js
create app/javascript/channels/comment_channel.js
生成された、app/channels/comment_channel.rbとapp/javascript/channels/comment_channel.jsが重要なファイルです。
comment_channel.rbの役割
👉クライアントとサーバーを結びつけるためのファイル。
comment_channel.jsの役割
👉サーバーから送られてきたデータをクライアントの画面に描画するためのファイル。
これらを編集しながら実装を進めます。
#③comment_channel.rbの編集
class CommentChannel < ApplicationCable::Channel
def subscribed
stream_from "comment_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
stream_form
👉ActionCableに用意されている、サーバーとクライアントを関連付けるメソッド。
#④comment_controller.rbの編集
class CommentsController < ApplicationController
(省略)
def create
@comment = Comment.new(comment_params)
if @comment.save
ActionCable.server.broadcast 'comment_channel', content: @comment
else
render :new
end
end
(省略)
end
broadcast
👉サーバーから送られるデータの経路のことを指す。stream_formメソッドに関連づけられるデータ経路。
追記している部分は、broadcastを通して、'comment_channel'に向けて@commentを送信するということです。
送信された情報は、comment_channel.jsで受け取ります。
#⑤comment_channel.jsの編集
import consumer from "./consumer"
consumer.subscriptions.create("CommentChannel", {
connected() {
// Called when the subscription is ready for use on the server
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
const html = `<p>${data.content.text}</p>`;
const comments = document.getElementById('comments');
const newComment = document.getElementById('text');
comments.insertAdjacentHTML('afterbegin', html);
newComment.value='';
}
});
受け取った情報は、receivedの引数dataに入ります。このデータをテンプレートリテラルにして、new.html.erbに挿入します。
const html = <p>${data.content.text}</p>
;
この部分で、受け取ったdataの中にあるcontentのなかのtextを表示しています。
contentはコントローラーのcreateアクション内で指定したcontentからきています。contentは@messageと同義のため、textを取り出すことができます。
以上で即時コメント機能の実装完了です。
#おわりに
Railsにおけるコメント機能の実装手順を見てみると、大半の方ははajaxで実装されているみたいですが、どうなんでしょうか?いろいろ調べながら知見を増やしていきます。