LoginSignup
1
2

More than 1 year has passed since last update.

【Railsチュートリアル】いいね機能の実装(アニメーション付)

Last updated at Posted at 2021-11-27

はじめに

前回の記事改【Railsチュートリアル】プロフィール画像をアプリ内で設定できるように変更に引き続きRailsチュートリアル第6版sample_appの機能拡張を進めていく。
今回は以下の様ないいね機能を実装していく。
instagramのいいねボタンによく似たハートが膨らむアニメーションを付けている。

like.gif

前提
第6版sample_appが完成している

Favoriteモデル

Favoriteテーブルを追加

$ rails g model Favorite user_id:integer micropost_id:integer

user_idとmicropost_idにインデックスを追加し、組み合わせがユニークであるという複合キーインデックスも追加する。

db/migrate/[timestamp]_create_favorites.rb
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.likesmicropost.liked_byを使えるようにする。

app/models/user.rb
class User < ApplicationRecord
  .
  .
  .
  has_many :favorites, dependent: :destroy
  has_many :likes, through: :favorites, source: :micropost
  .
  .
  .
end
app/models/micropost.rb
class Micropost < ApplicationRecord
  .
  .
  .
  has_many :favorites, dependent: :destroy
  has_many :liked_by, through: :favorites, source: :user
  .
  .
  .
end
app/models/favorite.rb
class Favorite < ApplicationRecord
  belongs_to :user
  belongs_to :micropost
  validates :user_id, presence: true
  validates :micropost_id, presence: true
end

ユーザーモデルにメソッドを追加する。

app/models/user.rb
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

ルーティングを追加する。

config/routes.rb
Rails.application.routes.draw do
  .
  .
  .
  resources :users do
    member do
      get :following, :followers , :likes
    end
  end
  .
  .
  .
end

likeページの作成

ステータスページにlike数を追加。

app/views/shared/_status.html.erb
<% @user ||= current_user %>
  .
  .
  .
  <a href="<%= likes_user_path(@user) %>">
    <strong id="likes" class="stat">
      <%= @user.likes.count %>
    </strong>
    likes
  </a>
</div>

スクリーンショット
ユーザーコントローラーにlikeアクションを追加。

app/controllers/users_controller.rb
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に変更している。

app/viwes/users/show_like.html.erb
<% 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>

スクリーンショット 2021-11-28 3.16.42.png

likeボタンの作成

yarnを用いてFontAwesomeをインストールする。

$ yarn add @fortawesome/fontawesome-free

以下の記述を追加する。

app/javascript/packs/application.js
.
.
.
import '@fortawesome/fontawesome-free/js/all';
app/assets/stylesheets/custom.scss
$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';
.
.
.

ルーティングを追加する。

config/routes.rb
Rails.application.routes.draw do
  .
  .
  .
  resources :favorites, only: [:create, :destroy]
end

マイクロポストパーシャルにrenderを追加する。

app/views/microposts/_micropost.html.erb
<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パーシャルを追加する。

app/viws/microposts/_like.html.erb
<span class="timestamp"><%=micropost.liked_by.count%>&nbsp;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 %>
app/viws/microposts/_unlike.html.erb
<span class="timestamp"><%=micropost.liked_by.count%>&nbsp;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を追加する。

app/assets/stylesheets/custom.scss
.
.
.
.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
app/controllers/favorites_controller.rb
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を追加する。

app/views/microposts/_like.html.erb
<%= form_for(current_user.favorites.build, remote: true) do |f| %>
.
.
.
app/views/microposts/_unlike.html.erb
<%= form_for(current_user.favorites.find_by(micropost_id: micropost.id),
             html: { method: :delete }, remote: true) do |f| %>
.
.
.

Favoriteコントローラーを変更する。

app/contorollers/favorites_contoroller.rb
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ファイルを作成する。

app/views/favorites/create.js.erb
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/unlike',
                micropost: @micropost)) %>");
app/views/favorites/destroy.js.erb
$("#likes").html('<%= @user.likes.count %>');
$("#like_form_<%= @micropost.id %>").html("<%= escape_javascript(render('microposts/like',
                micropost: @micropost)) %>");
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