5
5

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.

【Rails】Action Cableでコメント機能を実装しよう!

Posted at

Action Cableを利用してリアルタイムでコメントできる機能を実装します。
今回もレシピアプリを例に作成していきます。

#####完成イメージ
コメント.gif

##Action Cableとは

Action Cableは、 WebSocketとRailsのその他の部分をシームレスに統合するためのものです。Action Cable が導入されたことで、Rails アプリケーションの効率の良さとスケーラビリティを損なわずに、通常のRailsアプリケーションと同じスタイル・方法でリアルタイム機能をRubyで記述できます。クライアント側のJavaScriptフレームワークとサーバー側のRubyフレームワークを同時に提供する、フルスタックのフレームワークです。
出典:Railsガイド

なるほど、わからん!

つまりは、通常のRailsのアプリケーションと同様の記述で、即時更新機能を実装できるフレームワークで、メッセージの保存や送信するためにはRubyのコーディングが必要で、保存したメッセージを即時に表示させるためにはJavaScriptのコーディングが必要になります。

##コメント機能の実装
まずは、通常通りコメント機能を実装していきます。

####コメントモデルの作成

ターミナル
rails g model comment

今回、コメントをtext型でtextカラムとしreferences型でuserとrecipeに紐付けます

db/migrate/2021xxxxxxxxxx_create_comments
class CreateComments < ActiveRecord::Migration[6.0]
  def change
    create_table :comments do |t|
    #ここから追記
      t.text :text,         null: false
      t.references :user,   foreign_key: true
      t.references :recipe, foreign_key: true
    #ここまで追記
      t.timestamps
    end
  end
end
ターミナル
rails db:migrate

####アソシエーション、バリデーションの設定

app/models/user.rb
class User < ApplicationRecord
  has_many :recipes, dependent: :destroy 
  has_many :comments, dependent: :destroy #追記
#以下略
app/models/recipe.rb
class Recipe < ApplicationRecord
#中略 
  has_many :comments, dependent: :destroy #追記
  has_many_attached :images
#以下略
app/models/comment.rb
class Comment < ApplicationRecord
#ここから追記
  belongs_to :user
  belongs_to :recipe

  validates :text, presence: true
#ここまで追記
end

####コントローラーの作成、ルーティングの設定

ターミナル
rails g controller comments

コメントはレシピに紐づくのでネストさせます。

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  root to: 'home#index'
  resources :recipes do
    resources :comments #追記
    collection do
      get :search
      get :result
    end
  end
end

####コントローラーの編集
コントローラーにコメントを保存するための記述をしていきます。

app/controllers/recipes_controller.rb
class RecipesController < ApplicationController
#中略
  def show
    @recipe = Recipe.find(params[:id])
    @comment = Comment.new #追記
    @comments = @recipe.comments.order(created_at: :desc) #追記
  end
#以下略
end
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    @comment = Comment.new(comment_params)
    if @comment.valid?
      @comment.save
      redirect_to recipe_path(@comment.recipe)
    else
      redirect_to recipe_path(@comment.recipe)
    end
  end

  private
  def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, recipe_id: params[:recipe_id])
  end
end

####ビューファイルの編集

app/views/recipes/show.html.erb
#中略
<div class="show-comment">
    <div class="recipe-comments text-center">コメント一覧</div>
    <div id="comments">
      <% @comments.each do |comment| %>
        <div class="comment"><%= comment.text %></div>
      <% end %>
    </div>
    <div class="text-center">
      <%= form_with model: [@recipe, @comment],local: true do |f| %>
        <label class="text-secondary">コメントする</label><br>
        <%= f.text_field :text, class: "comment-form" %>
        <%= f.submit '送信', class: "btn btn-primary btn-sm" %>
      <% end %>
    </div>
  </div>

これでコメント機能の実装は完了です。
ここまでで一度、挙動を確認してみましょう。
f5958d56e5f33a539ae0d17e2a32bd8c.gif

このままでも機能的には問題ないですが、リロードに若干の時間がかかります。
Action Cableを使って即時更新してみましょう。

##Action Cableの実装
####Channel(チャネル)の作成
チャネルとは

チャネル (Channel) は、論理的な作業単位をカプセル化します。通常のMVC設定でコントローラが果たす役割と似ています。Railsはデフォルトで、チャネル間で共有されるロジックをカプセル化するApplicationCable::Channelという親クラスを作成します。
出典:Railsガイド

全くわからん!!!

ざっくりとした説明になりますが、チャネルはAction Cable専用のファイルでMVCのコントローラーと役割が似ています。

チャネルを作成していきましょう。

ターミナル
rails g channel comment

以下のようにファイルが生成されます。

ターミナル
Running via Spring preloader in process 39620
      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

ここで生成されたcomment_channel.rbがクライアントとサーバーを結びつけるためのファイルで、comment_channel.jsがサーバーから送られてきたデータをクライアントの画面に描画するためのファイルになります。

####comment_channel.rbの編集
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

####コントローラーの編集
redirect_toの記述を変更します。

app/controllers/comments_controller.rb
class CommentsController < ApplicationController
  def create
    @comment = Comment.new(comment_params)
    if @comment.valid?
      @comment.save
      ActionCable.server.broadcast "comment_channel", comment: @comment #変更
    else
      redirect_to recipe_path(@comment.recipe)
    end
  end

#以下略

broadcastはサーバーから送られてくるデータの経路を指し、comment_channel.rbのstream_fromメソッドと関連づいています。

@commentはcommentに格納されbroadcastを通じcomment_channel.rbのcomment_channelへ送られます。

そして、送られた情報は、comment_channel.jsで受け取ります。

####comment_channel.jsの編集
受け取った情報は、receivedの引数dataに渡されます。
この受け取ったdataを利用してコメントを描画します。

まず、dataをテンプレートリテラルにして定数htmlに格納します。
getElementByIdでコメントを表示させる場所のid "comments"を取得し定数commentsに格納します。
次にgetElementByIdでコメント入力フォームのidを取得し、定数newCommentに格納します。
そして、insertAdjacentHTMLで定数htmlを定数commentsに挿入します。
最後に入力フォームに入力された値をvalue = ''で削除します。

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.comment.text}</p>`;
    const comments = document.getElementById('comments');
    const newComment = document.getElementById('comment_text');
    comments.insertAdjacentHTML('afterbegin', html);
    newComment.value = '';
//ここまで追記
  }
});

以上で完成になります。

実際にコメントが投稿できるか確認してみましょう。
コメント.gif

5
5
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
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?