👕

いいね,コメント,DM機能を非同期で実装する

2023/09/30に公開

この記事で分かること

  • いいね機能の実装
  • コメント機能の実装
  • DM機能の実装

前提条件

  • bootstrapが実装済み
  • scssファイルが実装済み
  • jQueryが実装済み
  • アソシエーションができる(記事では割愛)

ER図

er_figure

テーブル

table_users

table_items

table_favorites

table_post_comment

table_room

table_user_room

table_chat

動作デモ

いいね機能

like_function_demo

コメント機能

post_comment_function_demo

DM機能

chat_function_demo

実装手順

  1. いいね機能を実装
  2. 吹き出しアイコンの実装 (参考記事:【Rails】チャットの吹き出しの向きを変える方法)
  3. コメント機能を実装
  4. DM機能を実装

実装方法

実装手順に沿って紹介します。

1.いいね機能の実装

Favoritesモデル・コントローラ・ビューを作成

$ rails g model Favorite user_id:integer item_id:integer
$ rails g controller favorites 

Routingの設定

config/routes
  scope module: :public do
    root to: "homes#top"
    get "/about" => "homes#about"
    get "search" => "searches#search"
    resources :items, only: [:new, :index, :show, :create, :destroy, :create] do
+     resource :favorites, only: [:create, :destroy]
      resources :post_comments, only: [:create, :destroy]
      resource :reports, only: [:new, :create]
      get :complete, on: :member
    end

Controllerの記述

public/favorites_controller.tb
  def create
    @item = Item.find(params[:item_id])
    favorite = current_user.favorites.new(item_id: @item.id)
    favorite.save
    <!-- リダイレクト先(非同期) -->
  end

  def destroy
    @item = Item.find(params[:item_id])
    favorite = current_user.favorites.find_by(item_id: @item.id)
    favorite.destroy
    <!-- リダイレクト先(非同期) -->
  end
end

Viewの記述

部分テンプレートを作成します。

public/favorites/_favorite.html.erb
<% if item.favorited_by?(current_user) %>
<p>
  <%= link_to item_favorites_path(item), method: :delete, remote: true do %>
   <i class="fa-solid fa-thumbs-up"></i><%= item.favorites.count %> いいね
  <% end %>
</p>
<% else %>
<p>
  <%= link_to item_favorites_path(item), method: :post, remote: true do %>
   <i class="fa-regular fa-thumbs-up"></i><%= item.favorites.count %> いいね
  <% end %>
</p>
<% end %>
  • remoteオブション・・・form系のヘルパーメソッド、記述することでAjaxを実装できる

非同期通信化

部分テンプレート化した投稿ページに部分テンプレートの呼び出し及びIDの設定を行います。

public/items/_postitem.html.erb
 <% items.each do |item| %>
  <div class="table-responsive-sm">
   <table class="table text-nowrap table-bordered w-100">
       <!-- 略 -->
       <tr class="align-middle text-center">
          <th class="head">コメント数</th>
         <td class="td"><%= item.post_comments.count %>件</td>
         <td class="td" colspan="2"><%= item.view_counts.count %>回表示</td>
+        <td class="td", id="favorite_buttons_<%= item.id %>"><%= render 'public/favorites/favorite', item: item %></td>
       </tr>
   </table>
   <!-- 略 -->
 <% end %>

favorites/create.js,destroy.jsを作成

favorites/create.js
$('#favorite_buttons_<%= @item.id %>').html("<%= j(render 'public/favorites/favorite', item: @item) %>");
favorites/destroy.js
$('#favorite_buttons_<%= @item.id %>').html("<%= j(render 'public/favorites/favorite', item: @item) %>");

2.吹き出しアイコンの実装

吹き出しアイコンは以下の記事を参考にして実装しました。

https://qiita.com/devmatsuko/items/f72a4da3868aeda9f3a1

3.コメント機能の実装

PostCommentsモデル・コントローラ・ビューを作成

$ rails g model PostComment user_id:integer item_id:integer comment:text
$ rails g controller PostComments 

Routingの設定

config/routes
  scope module: :public do
    root to: "homes#top"
    get "/about" => "homes#about"
    get "search" => "searches#search"
    resources :items, only: [:new, :index, :show, :create, :destroy, :create] do
      resource :favorites, only: [:create, :destroy]
+     resources :post_comments, only: [:create, :destroy]
      resource :reports, only: [:new, :create]
      get :complete, on: :member
    end

Controllerの記述

public/post_comments_controller.rb
class Public::PostCommentsController < ApplicationController
  before_action :authenticate_user!
  def create
    @item = Item.find(params[:item_id])
    @user = @item.user
    @post_comment = current_user.post_comments.new(post_comment_params)
    @post_comment.item_id = @item.id
    @post_comment.save
    <!-- リダイレクト先(非同期) -->
  end

  def destroy
    @post_comment = PostComment.find(params[:id]).destroy
    @post_comment.destroy
    @item = Item.find(params[:item_id])
    <!-- リダイレクト先(非同期) -->
  end

  private

  def post_comment_params
    params.require(:post_comment).permit(:comment)
  end
end
public/items_controller
  def show
    @item = Item.find(params[:id])
    @genres = Genre.all
    @products = Product.all
    unless ViewCount.find_by(user_id: current_user.id, item_id: @item.id)
      current_user.view_counts.create(item_id: @item.id)
    end
    @new_item = Item.new
+   @post_comment = PostComment.new
    @user = @item.user
  end

Viewの記述

部分テンプレートを作成します。

public/post_comments/_form.html.erb
<div class= "row">
  <div class= "col-6 offset-4 mt-5">
  <%= form_with model: [item, post_comment], local: false do |f| %>
    <%= f.text_area :comment, class: 'form-control', rows: '6', placeholder: "コメントをしよう" %><br>
    <%= f.submit "コメントする", class: "btn btn-outline-success" %>
  <% end %>
</div>
</div>
public/post_comments/_show.html.erb
<% item.post_comments.each do |post_comment| %>
  <div class= "row mt-3">
   <div class= "col-sm-2 col-4 text-center">
     <%= image_tag post_comment.user.get_profile_image(80,80),class: "img_icon" %><br>
   </div>
   <div class= "col-sm-10 col-8">
    <span><%= post_comment.user.name %>さんからのコメント</span><br>
      <div class="says">
       <span><%= post_comment.comment %></span>
     </div>
    </div>
  </div>
  <div class= "row">
   <div class= "col-md-4 col-6 offset-md-8 offset-6">
    <span><%= post_comment.created_at.strftime('%Y/%m/%d') %></span>
    <% if post_comment.user == current_user %>
     <%= link_to "削除する", item_post_comment_path(item, post_comment), method: :delete,remote:true, data: {"turbolinks" => false}, class: "text-dark"  %>
    <% end %>
   </div>
  </div>
<% end %>

非同期通信化

部分テンプレート化した投稿ページに部分テンプレートの呼び出し及びIDの設定を行います。

public/items/show

    <!-- 略 -->
    <div class='col-md-8 offset-md-1 mt-3'>
      <h4><%= @user.name %>さんの投稿</h4>
      <div class="col-12 table-responsive-sm">
        <table class="table text-nowrap table-bordered">
      <!-- 略 -->
        <tr class="align-middle text-center">
          <th class="head">コメント数</th>
+         <td class="td", id="comment_count"><%= @item.post_comments.count %><span></span></td>
          <td class="td" colspan="2"><%= @item.view_counts.count %>回表示</td>
          <td class="td", id="favorite_buttons_<%= @item.id %>"><%= render 'public/favorites/favorite', item: @item %></td>
        </tr>
       </table>
     </div>
     <div class="row justify-content-around text-center align-middle">
      <div class="col-lg-5 col-md-7 col-sm-7 col-8 mb-lg-5 my-sm-3 my-3 background"><strong><%= link_to "不適切な投稿として報告する" ,new_item_reports_path(@item), class: "text-dark" %></strong></div>
      <div class="col-lg-5 col-md-7 col-sm-7 col-8 mb-lg-5 my-sm-3 mb-4 background"><span>投稿日 </span><%= @item.created_at.strftime('%Y/%m/%d') %></div>
     </div>
+    <div id="post-comments">
      <%= render "public/post_comments/show" ,item: @item %><br>
    </div>
+   <div id="comment-form">
      <%= render "public/post_comments/form" ,item: @item , post_comment: @post_comment  %>
    </div>
   </div>
  </div>
 </div>

public/post_comments/create.js
$('#post-comments').html("<%= j(render "public/post_comments/show", item: @item) %>");
$('#comment_count').html("<%= @item.post_comments.count %>")
$("textarea").val('');
public/post_comments/destroy.js
$('#post-comments').html("<%= j(render "public/post_comments/show", item: @item) %>");
$('#comment_count').html("<%= @item.post_comments.count %>")

DM機能の実装

実装は以下の記事を参考にさせていただきました。
https://naskanoheya.com/catch-up/sns/

UserRoom,Room,Chatモデル・コントローラ・ビューを作成

$ rails g model UserRoom user_id:integer room_id:integer
$ rails g model Room 
$ rails g model Chat user_id:integer room_id:integer message:text
$ rails g controller chats
解説

LINEをイメージ

  • UserRoom・・・1つのグループに対してユーザは何人でも(今回は機能上2人)参加でき、ユーザは色々なグループに参加できるN:Mの関係なので中間テーブルが必要
  • Room・・・グループを管理するために必要(Aグループ,Bグループ...)
  • Chat・・・ユーザがどのグループでチャットしたか識別

Routingの設定

config/routes
  scope module: :public do
  resources :chats, only: [:show, :create]
  end

Controllerの記述

public/chats_controller.rb
 def show
   @user = User.find(params[:id]) #チャットする相手
   rooms = current_user.user_rooms.pluck(:room_id) #ログイン中のユーザーの部屋情報を全て取得
   user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)#その中にチャットする相手とのルームがあるか確認

   unless user_rooms.nil?
     @room = user_rooms.room #変数@roomにユーザー(自分と相手)と紐づいているroomを代入
   else
     @room = Room.new
     @room.save
     UserRoom.create(user_id: current_user.id, room_id: @room.id) #自分の中間テーブルを作成
     UserRoom.create(user_id: @user.id, room_id: @room.id) #相手の中間テーブルを作成
   end
   @chats = @room.chats
   @chat = Chat.new(room_id: @room.id)
 end

 def create
   @chat = current_user.chats.new(chat_params)
   @room = @chat.room
   @chats = @room.chats
   @chat.save
   <!-- リダイレクト先(非同期) -->
 end

 private
 def chat_params
   params.require(:chat).permit(:message, :room_id)
 end

 end

SNSのような機能を作ってみる/④コントローラの作成

whereとpluckの違い

where・・・ テーブル内の条件に一致したレコードを全て取得 / where(条件)
pluck・・・ データベースからデータを配列として取り出す / モデル.pluck(カラム名)

Viewの記述 / 非同期通信化

public/chats/show.html.erb
<div class="container py-3 px-sm-0">
  <div class= "row justify-content-center col-8 offset-2">
    <h4 class="text-center align-middle"><%= @user.name %>さんとのチャット</h4>
  </div>
  <div class= "row justify-content-center col-10 offset-1", id="post-chats">
      <%= render "public/chats/messages", chats: @chats %>
  </div> <!-- IDの設定をする -->
  <div class= "row justify-content-center col-10 offset-1 mt-5", id="chat-form">
      <%= render "public/chats/form", chat: @chat, room: @room %>
  </div> <!-- IDの設定をする -->
</div>
public/chats/_form.html.erb
<%= form_with model: chat, local: false do |f| %>
  <%= f.text_area :message, style: "width:100%", rows: '7', placeholder: "メッセージを入力してください" %>
  <%= f.hidden_field :room_id, value: room.id %>
  <%= f.submit "メッセージを送る", class: "btn btn-outline-success" %>
<% end %>
public/chats/messages.html.erb
    <% chats.each do |chat| %>
      <% if chat.user_id == current_user.id %>
       <div class="col-2 text-right mb-3 w-30">
         <%= image_tag chat.user.get_profile_image(80,80),class: "img_icon" %><br>
       </div>
       <div class= "col-10 mb-3 w-70">
         <span><%= chat.user.name %>さん</span><br>
        <div class="says text-center">
            <%= chat.message %>
        </div>
      </div>
      <% else %>
       <div class= "col-10 text-right mb-3 w-70">
         <span><%= chat.user.name %>さん</span><br>
        <div class="other-user-says text-center">
            <%= chat.message %>
        </div>
      </div>
       <div class="col-2 text-left mb-3 w-30">
         <%= image_tag chat.user.get_profile_image(80,80),class: "img_icon" %><br>
       </div>
      <% end %>
    <% end %>
public/chats/create.js
$('#post-chats').html("<%= j(render "public/chats/messages", chats: @chats) %>");
$("textarea").val('');

感想

非同期通信化を実装する手順を再確認できてよかったです。DN機能は実装時、あいまいな知識で実装したので記事を通して理解できてよかったです。

この記事をかいた人

https://twitter.com/tya_dwc
23/6/1にDWCに入学し、主にRailsの学習に取り組みました。卒業が近づきこれから何で学習をするか悩んだときに、技術ブログをしようと考えました。初心者ですがよろしくお願いします。

Discussion