【Ruby】いいね機能の実装 [初心者]  まとめ

#概要
投稿アプリ作成の際に「いいね機能」を実装しました。
いいね機能が動くまでの流れを忘れないために載せておきます！
環境構築・アプリ作成は既に済んでる状態で記載していきます。

#環境
ruby 2.6.5
Rails 6.0.3.5

#全体の流れ
今回は**「userテーブル」****「postsテーブル」****「likesテーブル」**の3つのテーブルで実装していきます。
**①【同期いいね】**
**②【いいね数の表示】**
**③【非同期いいね】**
**④【アイコンの使用】**


#手順①【同期いいね】

##likesテーブルの作成
ユーザーIDと投稿IDを保存するための「likesモデル」を作成。
カラムは外部キーの「user_id」「post_id」のみ。
以下のコマンドを実行

```
 % rails g model Like user_id:integer post_id:integer
 % rails db:migrate
```
このコマンド以外にもモデルの作成方法はあります。
以下のリンクを参考にしてください。
[モデルとテーブルを作成する（Ruby on Rails） -Qiita](https://qiita.com/pensuke628/items/52e0be46e8ca1091a056)
[Railsでモデルを生成する方法を現役エンジニアが解説【初心者向け】](https://techacademy.jp/magazine/22033)

##アソシエーションの定義
```ruby:user.rb
 class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :validatable
  has_many :posts
  has_many :likes # この1行を追加
end
```

```ruby:post.rb
 class Post < ApplicationRecord
  belongs_to :user
  has_many :likes  # この1行を追加
end
```

```ruby:like.rb
 class Like < ApplicationRecord
  belongs_to :user  # この1行を追加
  belongs_to :post  # この1行を追加
end
```

##いいねの表示
次にビューを編集します。
ビューはいいねを押すことができるリンクを作成します。

「views/posts/index.html.erb」を編集し、「いいねを外す」と「いいね」がそれぞれ表示されるように設定します。

```rs:views>posts>index.html.erb
  <% if current_user.liked_by?(post.id) %>
    <div>いいねを外す</div>
  <% else %>
    <div>いいね</div>
  <% end %>
```

「liked_by?(post.id)」はユーザーがいいねを押しているかどうかを判断するメソッドです。

##「liked_by?」メソッドの定義
likesテーブルに「post_id」が存在するかどうかの検索をかけます。

```ruby:user.rb
#下記を追加
def liked_by?(post_id)
  likes.where(post_id: post_id).exists?  
end
```

exists?メソッドは値が存在すればtrue、存在しない場合はfalseを返すメソッドです。

## いいねを押す

ここまででビューにいいねが表示されます。しかし、リンクを付与していないため「いいね」をクリックすることができません。なので次はいいねを押したり解除できる機能を実装していきます。

##likesコントローラー作成

```
% rails g controller likes
```
次にlikesコントローラーにcreateアクション(いいねを押す機能)を定義します。

```ruby:likes_controller.rb
class LikesController < ApplicationController
  def create
    Like.create(user_id: current_user.id, post_id: params[:id])
    redirect_to posts_path
  end
end
```

##ルーティングを作成

```ruby:routes.rb
Rails.application.routes.draw do
  devise_for :users
  root 'home#index'
  resources :posts
  post 'like/:id' => 'likes#create', as: 'create_like' #この1行を追加
```

% rails routesを実行すると「create_like POST /like/:id(.:format) likes#create」が出力されます。

次に「いいね」にリンクを付与するためにビューを編集します。

```rs:views>posts>index.html.erb
<div>いいね</div>

↓ 以下のように編集

<div><%= link_to 'いいね', create_like_path(post), method: :POST %></div>
```
これで「いいね」をクリックすることができるようになりました。
create_like_path　はルーティング作成の時に「as: 'create_like'」でパス名を指定しています。

##いいねを解除する
次にいいねを解除する機能を実装します。
likes_controller.rbにdestroyアクション(いいねを解除する機能)を定義します。

```ruby:likes_controller.rb
class LikesController < ApplicationController
  def create
    Like.create(user_id: current_user.id, post_id: params[:id])
    redirect_to posts_path
  end

  #下記を追加
  def destroy
    Like.find_by(user_id: current_user.id, post_id: params[:id]).destroy
    redirect_to posts_path
  end
end
```
「find_byメソッド」は複数の検索条件を指定することができるメソッドです。

ルーティングも編集しましょう。

```ruby:routes.rb
Rails.application.routes.draw do
  devise_for :users
  root 'home#index'
  resources :posts
  post 'like/:id' => 'likes#create', as: 'create_like'
  delete 'like/:id' => 'likes#destroy', as: 'destroy_like'  #この1行を追加
end
```
rails routesを実行すると「destroy_like DELETE /like/:id(.:format) likes#destroy」が出力されます。

次に「いいねを外す」にリンクを付与するため、ビューを編集します。

```rs:views>posts>index.html.erb
<div>いいねを外す</div>

↓ 以下のように編集

<div><%= link_to 'いいねを外す', destroy_like_path(post), method: :DELETE %></div>
```

これで「いいねを外す」をクリックすることができるようになりました。
destroy_like_path　はルーティング作成の時に「as: 'destroy_like'」でパス名を指定しています。

これでいいねを解除することができるようになりました。

##挙動確認
これで【同期いいね】の実装は完了です。挙動を確認してみましょう。
クリックするごとに「いいね」→「いいねを外す」→「いいね」→「いいねを外す」と表示が変更されていれば成功です。

#実装手順②【いいね数の表示】
次にいいねが押された数を表示させましょう。

いいね数の取得には「countメソッド」を使用します。
「countメソッド」を使用することで簡単に実装することができます。
index.html.erbを以下のように編集しましょう。

```rs:views>posts>index.html.erb
<% if current_user.liked_by?(post.id) %>
  <div><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %></div>
<% else %>
  <div><%= link_to 'いいね', create_like_path(post), method: :POST %></div>
<% end %>

↓ 以下のように編集

<% if current_user.favorited_by?(post.id) %>
  <div><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></div>
<% else %>
  <div><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></div>
<% end %>
```

これで【いいね数の表示】は完了です。
いいねの右側に「いいね」の数が表示されるようになりました。

#実装手順③【非同期いいね】
次に非同期でのいいねを実装していきます。
少し実装が複雑になるので実装全体の内容を話します。

##全体の流れ
**⑴それぞれの投稿を部分テンプレートで切り出す。**
　　　　　↓
**⑵JavascriptでHTML構造を置き換える。(「いいね」「いいねを外す」を押した瞬間の時)**
　　　　　↓
**⑶それぞれの投稿の部分テンプレートに異なるidを付与して判別する**

では全体の流れを把握した上で実装していきましょう。

##部分テンプレートの切り出し

まずは「いいね」の箇所を部分テンプレートで切り出します。いいね部分を切り分けることでその部分だけを変更できるようにしています。

```rs:views>posts>index.html.erb
  <% @posts.each do |post| %>
      <% if current_user.liked_by?(post.id) %>
        <div><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></div>
      <% else %>
        <div><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></div>
      <% end %>
  <% end %>

↓以下のように変更

  <% @posts.each do |post| %>
      <%= render 'post', post: post %>
  <% end %>
```

```rs:views>posts>_post.html.erb
<% if current_user.liked_by?(post.id) %>
  <div><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></div>
<% else %>
  <div><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></div>
<% end %>
```

##remote:trueの追加
次に以下のようにlink_toメソッドに「remote:true」を追加します。
【非同期いいね】では「remote:true」が必ず必要になります。

```rust:views>posts>_post.html.erb
<% if current_user.liked_by?(post.id) %>
  <div><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE %> <%= post.likes.count %></div>
<% else %>
  <div><%= link_to 'いいねする', create_like_path(post), method: :POST %> <%= post.likes.count %></div>
<% end %>

↓ 以下のように編集

<% if current_user.liked_by?(post.id) %>
  <div><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE, remote: true %> <%= post.likes.count %></div>
<% else %>
  <div><%= link_to 'いいねする', create_like_path(post), method: :POST, remote: true %> <%= post.likes.count %></div>
<% end %>
```

「remote: true」を追加することで、パラメーターがHTML形式ではなくJS形式で送られるようになります。
今回は「remote: true」をlink_toメソッドに追加したことで、likes_controller.rbのcreateアクション後は、views/likes/create.js.erbが呼び出されることになります。

##redirect_toの削除
「redirect_to」を記述すると画面遷移が行われてしまい、非同期処理ができなくなってしまいます。
そのため、likes_controller.rbの「redirect_to」の記述を削除します。

さらに、「before_action」を使用して、投稿のidを取得できるようにしましょう。

```ruby:likes_controller.rb
class LikesController < ApplicationController
  def create
    Like.create(user_id: current_user.id, post_id: params[:id])
    redirect_to posts_path
  end

  def destroy
    Like.find_by(user_id: current_user.id, post_id: params[:id]).destroy
    redirect_to posts_path
  end
end

↓ 以下のように変更

class LikesController < ApplicationController
  before_action :post_params

  def create
    Like.create(user_id: current_user.id, post_id: params[:id])
  end

  def destroy
    Like.find_by(user_id: current_user.id, post_id: params[:id]).destroy
  end

  private

  def post_params
    @post = Post.find(params[:id])
  end
end
```

##js.erbファイルの作成
次に、 ⑵JavascriptでHTML構造を置き換える。(「いいね」「いいねを外す」を押した瞬間) の実装を行います。
createアクションとdestroyアクションのjs.erbファイルをそれぞれ作成しましょう。
(js.erbファイルが発火するか確認するため、alertを記載します)

```ruby:views>likes>create.js.erb
alert('いいねを押す！')；
```
```ruby:views>likes>destroy.js.erb
alert('いいねを解除する！')；
```
現時点でクリックした際に「非同期アラート」が表示されていればOKです。

#idの付与
⑶それぞれの投稿の部分テンプレートに異なるidを付与して判別する を実装していきます。
部分テンプレートを切り替える部分にidを付与します。
具体的には「id=”post_<%= post.id %>”」と記述することで、投稿それぞれに異なるidを付与することができます。

```rs:views>posts>index.html.erb
<% @posts.each do |post| %>
   <%= render 'post', post: post %>
<% end %>

↓ idを付与

<% @posts.each do |post| %>
  <div id="post_<%= post.id %>">
    <%= render 'post', post: post %>
  </div>
<% end %>
```
これで部分テンプレートを切り替える際にidを付与することができるようになりました。

##js.erbファイルの編集
部分テンプレートの切り替えができるようにそれぞれのjs.erbファイルを編集します。
「innerHTMLメソッド」を使用することで、特定のHTML要素を置き換えることができます。

-参考リンク-
[要素の中身を変える！JavaScriptでinnerHTMLの使い方【初心者向け】](https://techacademy.jp/magazine/15332)


```ruby:views>likes>create.js.erb
document.getElementById('post_<%= @post.id %>').innerHTML = '<%= j(render @post) %>'
```

```ruby:views>likes>destroy.js.erb
document.getElementById('post_<%= @post.id %>').innerHTML = '<%= j(render @post) %>'
```
「j」とは「escape_javascriptメソッド」の省略形です。部分テンプレート内の改行をエスケープ処理してくれます。
省略せず、以下のように記述することもできます。

```ruby
document.getElementById('post_<%= @post.id %>').innerHTML = '<%= escape_javascript(render @post) %>'
```
##挙動確認
これで【非同期いいね】の実装が完了しました。
正しく「いいね」と「いいねの数」がクリックごとに変更されていれば成功です。

#実装手順④【アイコン使用】
いいねの基本的な動作は完了しました。
次にSNSで使用されているアイコンを用いたいいね機能を実装していきます。
アイコンはFontAwesomeを使用します。
FontAwesome：https://fontawesome.com/

##FontAwsomeの導入
まずはFontAwesomeの導入から行います。

Gemの導入。

```ruby:Gemfile
gem 'font-awesome-sass'
```
```
% bundle install
```

次に「application.css」の名前を「application.scss」に変更します。

さらに以下のコードを追加しましょう。(既にあるコードは消さないで下さい)

```ruby:application.css　→　application.scss
@import "font-awesome-sprockets";  
@import "font-awesome";
```
これでFontAwesomeを使用することができるようになります。

##アイコンの表示
次にアイコンをビューで表示します。
「いいね」と「いいね解除」を区別するためにそれぞれのアイコンに「like-btn」と「unlike-btn」というクラス名を追加します。
また、link_toメソッドにも「like_link」というクラスを追加しています。

```rs:views>posts>_post.html.erb
<% if current_user.liked_by?(post.id) %>
  <div><%= link_to 'いいね外す', destroy_like_path(post), method: :DELETE, remote: true %> <%= post.likes.count %></div>
<% else %>
  <div><%= link_to 'いいねする', create_like_path(post), method: :POST, remote: true %> <%= post.likes.count %></div>
<% end %>

↓ 以下のように編集

<% if current_user.liked_by?(post.id) %>
  <div>
    <%= link_to destroy_like_path(post), class: "like-link", method: :DELETE, remote: true do %>
      <i class="fa fa-heart unlike-btn"></i>
    <% end %>
    <%= post.likes.count %>
  </div>
<% else %>
  <div>
    <%= link_to create_like_path(post), class: "like-link", method: :POST, remote: true do %>
      <i class="fa fa-heart like-btn"></i>
    <% end %>
    <%= post.likes.count %>
  </div>
<% end %>
```
「link_to do --- end」を使用することで、アイコンがリンク化されます。

##デザインの変更
次にCSSを用いて、アイコンの色を変更します。

```css:likes.scss
.like-link{
   text-decoration: none;
}

.like-link:hover {
  background-color: #fff!important;
}

.like-btn {
  font-size: 10px;
  color: #C0C0C0;
}
 
.unlike-btn {
  font-size: 15px;
  color: #FF0000;
}
```

Scaffoldでアプリ作成すると以下のような記述がデフォルトで入る場合があります。

```css
a {
  color: #000;

  &:visited {
    color: #666;
  }

  &:hover {
    color: #fff;
    background-color: #000;
  }
}
```

アイコンをhoverした際に背景が黒になるのを避けるため、「!important」を追加してコードを上書きします。

```css
.like-link:hover {
  background-color: #fff!important;
}
```

##挙動確認
これでアイコンでのいいね表示が完了しました。

**いいね機能の実装はこれで全て完了です！**

#参考リンク
[いいね機能 ❏Rails❏ -Qiita](https://qiita.com/ITmanbow/items/2bb24864708fc7796272)
[【Rails】いいね機能の実装 -Qiita](https://qiita.com/soehina/items/a68ab66da3ea1d260301)

#最後に
今回は「同期いいね」「いいね数の表示」「非同期いいね」「アイコン使用」の4つの流れで実装していきました。
今後も時より復習していこうと思います。
振り返るといいね機能は簡単に実装できるので、ぜひ自身のアプリにも使用してみて下さい！！