👕
いいね,コメント,DM機能を非同期で実装する
この記事で分かること
- いいね機能の実装
- コメント機能の実装
- DM機能の実装
前提条件
- bootstrapが実装済み
- scssファイルが実装済み
- jQueryが実装済み
- アソシエーションができる(記事では割愛)
ER図
テーブル
動作デモ
いいね機能
コメント機能
DM機能
実装手順
- いいね機能を実装
- 吹き出しアイコンの実装 (参考記事:【Rails】チャットの吹き出しの向きを変える方法)
- コメント機能を実装
- 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.吹き出しアイコンの実装
吹き出しアイコンは以下の記事を参考にして実装しました。
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機能の実装
実装は以下の記事を参考にさせていただきました。
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.rbdef 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
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機能は実装時、あいまいな知識で実装したので記事を通して理解できてよかったです。
この記事をかいた人
23/6/1にDWCに入学し、主にRailsの学習に取り組みました。卒業が近づきこれから何で学習をするか悩んだときに、技術ブログをしようと考えました。初心者ですがよろしくお願いします。
Discussion