1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails6】Action Cableを用いた即時コメント機能の実装

Last updated at Posted at 2020-12-21

#はじめに

今回は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
db/migrate/xxxxxxxxxxxxxx_create_comments.rb
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

アソシエーションの設定

app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :article
  belongs_to :user

  validates :text, presence: true
end
app/models/article.rb
class Article < ApplicationRecord
 (省略)
  has_many :comments, dependent: :destroy
end
app/models/user.rb
class User < ApplicationRecord
 (省略)
  has_many :comments, dependent: :destroy
end

ルーティングの設定

config/routes.rb
Rails.application.routes.draw do
 (省略)
  resources :articles, only: [:index, :show] do
    resources :comments, only: [:new, :create]
  end
end

コントローラーの設定

app/controllers/comments_controller.rb
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の編集

app/views/comments/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の編集

app/channels/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の編集

app/controllers/comments_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の編集

app/javascript/channels/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で実装されているみたいですが、どうなんでしょうか?いろいろ調べながら知見を増やしていきます。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?