【Rails6】いいね機能（同期→非同期）実装

#はじめに

転職活動用のポートフォリオ作成中です。今回、ユーザーが記事に対してお気に入り登録ができるいいね機能を実装しました。まずは、同期で機能しているかを確認し、その後非同期に移る流れです。備忘録及び復習のため、記事を書いています。

#環境

Ruby on Rails '6.0.0'
Ruby '2.6.5'
FontAwesome導入

#前提

・ユーザー管理機能(userモデル）は実装済みで、新規登録やログインができる
・記事投稿機能(articleモデル）は実装済みで、ユーザーは記事の閲覧ができる

#①モデルの記述（同期いいね）

```ruby:ターミナル
% rails g model like
```

Likeモデルを作成し、マイグレーションファイルの記述を行います。今回は、ユーザーIDと投稿記事IDを保存するために、カラムは外部キーであるuse_idとarticle_idになります。

```ruby:db/migrate/20201111111111_create_likes.rb
class CreateLikes < ActiveRecord::Migration[6.0]
  def change
    create_table :likes do |t|
      t.references :article, null: false, foreign_key: true
      t.references :user, null: false, foreign_key: true
      t.timestamps
    end
  end
end
```

また、アソシエーションについては、下記の図のような形になるため、
(1人ユーザーは複数のいいねをすることができて、1つの記事に対しては、複数のいいねができる）

![ER (2)](https://user-images.githubusercontent.com/72916511/102424273-28bc7c00-404e-11eb-8c63-2b8cd49a1bc7.png)

```ruby:app/models/like.rb
class Like < ApplicationRecord
  belongs_to :article
  belongs_to :user
end
```

```ruby:app/models/user.rb
（中略）
  has_many :article
  has_many :likes, dependent: :destroy
```

```ruby:app/models/article.rb
(中略）
  has_many :likes, dependent: :destroy
```

「dependent: :destroy」を記述することで、記事が削除された時orユーザーが削除された時に、紐づくいいねも削除されるようになります。

また、今回、「liked_by?」メソッドをuserモデルに定義することで、ユーザーがいいねを押しているかどうかを判別します。

```ruby:app/models/user.rb
(中略)
has_many :article
has_many :likes, dependent: :destroy

def liked_by?(article_id)
  likes.where(article_id: article_id).exists?
end
```

whereメソッドを使用することで、likesテーブルに「article_id」が存在しているかどうか探しています。またexists?メソッドは、該当の値があれば「true」、なければ「
false」を返すメソッドです。テーブルとのやりとりに関するメソッドについては、モデルに置くそうです。。

#②コントローラーの記述（同期いいね）

```ruby:ターミナル
% rails g controller likes
```

コマンドを実行しlikesコントローラーを生成します。作成したコントローラーにcreateアクション・destroyアクションを記述し、いいねの付与と解除を設定します。

```ruby:app/controllers/likes_controller.rb
class LikesController < ApplicationController

  def create
    Like.create(user_id: current_user.id, article_id: params[:id])
    redirect_to root_path
  end

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

end
```

destroyアクションに使用している、find_byメソッドは複数の検索条件を指定することができるメソッドです。

#③ルーティングの記述

```ruby:config/routes.rb
post 'like/:id' => 'likes#create', as: 'create_like'
delete 'like/:id' => 'likes#destroy', as: 'destroy_like'
```

asオプションを使うことで、ルーティングに名前を設定できます。

#④ビューファイルの記述

今回、私の実装では複数箇所にいいね機能を実装したいと思っているため、部分テンプレートを使用し、likeのビューを記述しています。

```ruby:app/views/likes/_like.html.erb
<div class="likes_buttons">
<% if user_signed_in? %>
  <% if current_user.liked_by?(@article.id) %>
    <%= link_to destroy_like_path(@article.id, current_user.likes.find_by(article_id: @article.id).id), method: :delete do %>
      <p class="like-button"><i class="fas fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
    <% end %>
  <% else %>
    <%= link_to create_like_path(@article.id), method: :post do %>
      <p class="like-button"><i class="far fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
    <% end %>
  <% end %>
<% end %>
</div>
```

FontAwesomeを使用してハートの絵文字を表示しています。また、<%= @article.likes.count %>でいいね数の表示をしています。

以上で同期いいねは完了です。続いて非同期に移ります。

#⑤ビューファイルの編集

非同期実装するためには、先ほどの記述からlink_toメソッドに「remote: true」を付与します。remote: trueを付与することで、パラメーターがHTML形式ではなくJS形式で送られるようになるみたいです。今回は付与することで、likes_controller.rbのcreateアクション後は、views/likes/create.js.erbが呼び出されます。

```ruby:app/views/likes/_like.html.erb
<div class="likes_buttons">
<% if user_signed_in? %>
  <% if current_user.liked_by?(@article.id) %>
    <%= link_to destroy_like_path(@article.id, current_user.likes.find_by(article_id: @article.id).id), method: :delete, remote: true do %>
      <p class="like-button"><i class="fas fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
    <% end %>
  <% else %>
    <%= link_to create_like_path(@article.id), method: :post, remote: true do %>
      <p class="like-button"><i class="far fa-heart fa-2x" style="color: #e82a2a;"></i><span style="color: #e82a2a"><%= @article.likes.count %></span></p>
    <% end %>
  <% end %>
<% end %>
</div>
```

#⑥コントローラーの編集

ここで、コントローラーに記述をしていた「redirect_to」の記述を削除しました。そのままにしていると、画面遷移が行われ、非同期処理ができなくなるためです。
また、「before_action」を使用して、投稿のidを取得できるようにします。

```ruby:app/controllers/likes_controller.rb
class LikesController < ApplicationController
  before_action :article_params
  def create
    Like.create(user_id: current_user.id, article_id: params[:id])
  end

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

  def article_params
    @article = Article.find(params[:id])
  end
end
```

#⑦js.erbファイルの記述

以下の2つのファイルを生成し、同様の記述をしていきます。

```ruby:app/views/likes/create.js.erb
$('.likes_buttons').html("<%= j(render partial: 'likes/like', locals: {article: @article}) %>");
```

```ruby:app/views/likes/destroy.js.erb
$('.likes_buttons').html("<%= j(render partial: 'likes/like', locals: {article: @article}) %>");
```

likes_buttonsの部分のHTMLだけ、renderで部分的に更新する処理を記述しています。

あとは好きな部分で部分テンプレートを呼び出してもらえれば完成です。

```ruby:app/views/articles/show.html.erb
<%= render partial: "likes/like", locals: { article:@article } %>
```

#⑧Rails6におけるjQueryの環境設定

Rails6からwebpackerというものが標準となったみたいで、jQueryの導入になかなか苦戦したため、最後に記述します。
前提としてyarnというものがmac内にすでにインストールされているものとします。また今回私の実装ではbootstrap４も使用していますので、その前提で進めていきます。

```ruby:ターミナル
% yarn add bootstrap jquery popper.js
```

まず、上記コマンドを作成中のアプリケーションディレクトリで実行します。

次に、導入する記述をしていきます。

```ruby:app/javascript/packs/application.js
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
...
//この下を記入
require("bootstrap");
...
```

bootstrapとjQueryは依存関係にあるため、「require("bootstrap")」の記述で同時に「require("jquery")」も記述したようになるみたいです。

最後にwebpackerの記述をしていきます。

```ruby:config/webpack/environment.js
const { environment } = require('@rails/webpacker')

const webpack = require('webpack');

environment.plugins.prepend(
  'Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery',
    Popper: ['popper.js', 'default']
  })
)

module.exports = environment
```

以上で、Rails上でbootstrap,jQueryを使用することができるようになりました。

#最後に

わかりにくい表現もあると思いますが、適宜修正を加えていきたいと思います。

今回参考にさせていただいた記事は以下です。ありがとうございました。
https://techtechmedia.com/favorite-function-rails/#i-2
https://qiita.com/naberina/items/c6b5c8d7756cb882fb20