#はじめに
前回の記事改【Railsチュートリアル】プロフィール画像をアプリ内で設定できるように変更に引き続きRailsチュートリアル第6版sample_appの機能拡張を進めていく。
今回は以下の様ないいね機能を実装していく。
instagramのいいねボタンによく似たハートが膨らむアニメーションを付けている。
前提
第6版sample_appが完成している
#Favoriteモデル
Favoriteテーブルを追加
$ rails g model Favorite user_id:integer micropost_id:integer
user_idとmicropost_idにインデックスを追加し、組み合わせがユニークであるという複合キーインデックスも追加する。
class CreateFavorites < ActiveRecord::Migration[6.1]
def change
create_table :favorites do |t|
t.integer :user_id
t.integer :micropost_id
t.timestamps
end
add_index :favorites, :user_id
add_index :favorites, :micropost_id
add_index :favorites, [:user_id, :micropost_id], unique: true
end
end
$ rails db:migrate
ユーザーとマイクロポストに、Favoriteに対して1対多の関連づけを追加し、またhas_many through
を用いてuser.likes
とmicropost.liked_by
を使えるようにする。
class User < ApplicationRecord
.
.
.
has_many :favorites, dependent: :destroy
has_many :likes, through: :favorites, source: :micropost
.
.
.
end
class Micropost < ApplicationRecord
.
.
.
has_many :favorites, dependent: :destroy
has_many :liked_by, through: :favorites, source: :user
.
.
.
end
class Favorite < ApplicationRecord
belongs_to :user
belongs_to :micropost
validates :user_id, presence: true
validates :micropost_id, presence: true
end
ユーザーモデルにメソッドを追加する。
class User < ApplicationRecord
.
.
.
# マイクロポストをライクする
def like(micropost)
likes << micropost
end
# マイクロポストをライク解除する
def unlike(micropost)
favorites.find_by(micropost_id: micropost.id).destroy
end
# 現在のユーザーがライクしていたらtrueを返す
def likes?(micropost)
likes.include?(micropost)
end
private
.
.
.
end
ルーティングを追加する。
Rails.application.routes.draw do
.
.
.
resources :users do
member do
get :following, :followers , :likes
end
end
.
.
.
end
#likeページの作成
ステータスページにlike数を追加。
<% @user ||= current_user %>
.
.
.
<a href="<%= likes_user_path(@user) %>">
<strong id="likes" class="stat">
<%= @user.likes.count %>
</strong>
likes
</a>
</div>
class UsersController < ApplicationController
.
.
.
def likes
@title = "Likes"
@user = User.find(params[:id])
@microposts = @user.likes.paginate(page: params[:page])
render 'show_like'
end
private
.
.
.
end
ビューを作成する。
gravatarの部分はこちらの記事でimage_tagに変更している。
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<% if @user.avatar? %>
<%= image_tag @user.avatar.url, width: "100px", height: "100px", class:"icon"%>
<% else%>
<%= image_tag "default_user.jpeg", width: "100px", height: "100px", class:"icon"%>
<% end %>
<h1><%= @user.name %></h1>
<span><%= link_to "view my profile", @user %></span>
<span><b>Microposts:</b> <%= @user.microposts.count %></span>
</section>
<section class="stats">
<%= render 'shared/stats' %>
</section>
</aside>
<div class="col-md-8">
<h3><%= @title %></h3>
<% if @user.likes.any? %>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
#likeボタンの作成
yarnを用いてFontAwesomeをインストールする。
$ yarn add @fortawesome/fontawesome-free
以下の記述を追加する。
.
.
.
import '@fortawesome/fontawesome-free/js/all';
$fa-font-path: '@fortawesome/fontawesome-free/webfonts';
@import '@fortawesome/fontawesome-free/scss/fontawesome';
@import '@fortawesome/fontawesome-free/scss/solid';
@import '@fortawesome/fontawesome-free/scss/regular';
@import '@fortawesome/fontawesome-free/scss/brands';
@import '@fortawesome/fontawesome-free/scss/v4-shims';
.
.
.
ルーティングを追加する。
Rails.application.routes.draw do
.
.
.
resources :favorites, only: [:create, :destroy]
end
マイクロポストパーシャルにrenderを追加する。
<li id="micropost-<%= micropost.id %>">
.
.
.
</span> <span class="like" id="like_form_<%= micropost.id %>">
<% if micropost.liked_by.include?(current_user) %>
<%= render "microposts/unlike", micropost: micropost %>
<% else %>
<%= render "microposts/like", micropost: micropost %>
<% end %>
</span>
</li>
likeパーシャル、unkikeパーシャルを追加する。
<span class="timestamp"><%=micropost.liked_by.count%> like</span>
<%= form_for(current_user.favorites.build, remote: true, class:"btnlike") do |f| %>
<div><%= hidden_field_tag :micropost_id, micropost.id %></div>
<%= button_tag(class: "btn-like" ) do %>
<i class="far fa-heart heart-empty "></i> <% end %>
<% end %>
<span class="timestamp"><%=micropost.liked_by.count%> like</span>
<%= form_for(current_user.favorites.find_by(micropost_id: micropost.id),
html: { method: :delete }, remote: true) do |f| %>
<%= button_tag( class: "btn-like") do %>
<i class="fas fa-heart heart"></i>
<% end %>
<% end %>
cssを追加する。
.
.
.
.heart {
color: rgb(255, 74, 74);
font-size: 25px;
animation: anime 1s;
}
@keyframes anime{
15% { transform: scale(1.3); }
20% { transform: scale(1); }
}
.heart-empty {
font-size: 25px;
-webkit-text-stroke: 5px white
}
.btn-like:hover > .heart-empty {
color: rgb(168, 168, 168);
}
.btn-like {
position: relative;
left: 60px;
}
.btn-like {
border: none;
background-color: rgb(255, 255, 255);
:hover {
background-color: rgb(255, 255, 255);
}
}
Favoriteコントローラーを作成し、以下を記述する。
$ rails g controller Favorites
class FavoritesController < ApplicationController
before_action :logged_in_user
def create
micropost = Micropost.find(params[:micropost_id])
current_user.like(micropost)
redirect_back(fallback_location: root_url)
end
def destroy
micropost = Favorite.find(params[:id]).micropost
current_user.unlike(micropost)
redirect_back(fallback_location: root_url)
end
end
#Ajaxによる非同期化
like_html.erbとunlike_html.erbにremote:true
を追加する。
<%= form_for(current_user.favorites.build, remote: true) do |f| %>
.
.
.
<%= form_for(current_user.favorites.find_by(micropost_id: micropost.id),
html: { method: :delete }, remote: true) do |f| %>
.
.
.
Favoriteコントローラーを変更する。
class FavoritesController < ApplicationController
before_action :logged_in_user
def create
@user = current_user
@micropost = Micropost.find(params[:micropost_id])
current_user.like(@micropost)
respond_to do |format|
format.html { redirect_back(fallback_location: root_url) }
format.js
end
end
def destroy
@user = current_user
@micropost = Favorite.find(params[:id]).micropost
current_user.unlike(@micropost)
respond_to do |format|
format.html { redirect_back(fallback_location: root_url) }
format.js
end
end
end
JS-ERbファイルを作成する。
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/unlike',
micropost: @micropost)) %>");
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/like',
micropost: @micropost)) %>");