【Ruby on Rails】ブックマーク（お気に入り登録、いいね）機能：一方向

# 目標
投稿に対して、ブックマーク（お気に入り登録、いいね）機能を実装します。
![bookmark.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/700999/04c41b7e-06e0-9a93-0aa8-31438d42bc4b.gif)

# 開発環境
ruby 2.5.7
Rails 5.2.4.3
OS: macOS Catalina

# 前提
※　▶◯◯　を選択すると、説明等が出てきますので、
　　よくわからない場合の参考にしていただければと思います。

- [deviseでログイン環境構築](https://qiita.com/japwork/items/275110b810965fbdf72f)
- [ログインユーザーのみができる投稿機能](https://qiita.com/japwork/items/8dfb52121036791ac4fb)
- [投稿の編集機能（更新、削除）](https://qiita.com/japwork/items/e258ccbbe4f48ea5a3b5)

※[双方向のフォロー機能はこちら](https://qiita.com/japwork/items/77cf5f12522f6b91ae00)

# モデルの作成

```:ターミナル
$ rails g model Bookmark user:references post:references
```
null: falseを追加し、データベースへの空の保存を防ぐ。

```rb:db/migate/xxxxxxxxxxxxxx_create_bookmarks.rb
class CreateBookmarks < ActiveRecord::Migration[5.2]
  def change
    create_table :bookmarks do |t|
      t.references :user, foreign_key: true, null: false
      t.references :post, foreign_key: true, null: false

      t.timestamps
    end
  end
end
```

```:ターミナル
$ rails db:migrate
```
各モデルにそれぞれ追加。

```rb:app/models/bookmark.rb
validates :user_id, uniqueness: { scope: :post_id }
```
<details><summary>補足</summary>
上記validatesを追加することで、重複しての登録を防ぎます。
具体的には、ロード中に２度以上連続で登録しようとすることを防ぎます。</details>

```rb:app/models/post.rb
  has_many :bookmarks, dependent: :destroy

  def bookmarked_by?(user)
    bookmarks.where(user_id: user).exists?
  end
```
```rb:app/models/user.rb
has_many :bookmarks, dependent: :destroy
```
<details><summary>補足</summary>
bookmarked_by?(user)を追加することで、
既にブックマークしているかを検証します。</details>

# コントローラーの作成

```:ターミナル
$ rails g controller bookmarks
```

```rb:app/controllers/bookmarks_controller.rb
class BookmarksController < ApplicationController
  before_action :authenticate_user!

  def create
    @post = Post.find(params[:post_id])
    bookmark = @post.bookmarks.new(user_id: current_user.id)
    if bookmark.save
      redirect_to request.referer
    else
      redirect_to request.referer
    end
  end

  def destroy
    @post = Post.find(params[:post_id])
    bookmark = @post.bookmarks.find_by(user_id: current_user.id)
    if bookmark.present?
    	bookmark.destroy
    	redirect_to request.referer
    else
    	redirect_to request.referer
    end
  end
end
```
<details><summary>補足</summary>
まずはpost_idを取得し、その後user_idにcurrent_userを紐付けています。
bookmark.present?を挟んでいるのは、２度押しのエラーを回避するためです。</details>


```rb:config/routes.rb
  resources :posts, except: [:index] do
    resource :bookmarks, only: [:create, :destroy]
  end
```
<details><summary>補足</summary>
postにネストさせています。</details>

# viewの修正
```erb:app/views/posts/show.html.erb
<tbody>
  <tr>
    <td><%= @post.user.name %></td>
    <td><%= @post.title %></td>
    <td><%= @post.body %></td>
    <td><%= link_to "編集", edit_post_path(@post) %></td>
    <% if @post.bookmarked_by?(current_user) %>
      <td><%= link_to "ブックマークを外す", post_bookmarks_path(@post), method: :delete %></td>
    <% else %>
      <td><%= link_to "ブックマーク", post_bookmarks_path(@post), method: :post %></td>
    <% end %>
  </tr>
</tbody>
```

# 一覧表示させるためには
表示したい場所のコントローラーで下記を記述。
今回はapp/views/homes/mypage.html.erbに表示することとする。

```erb:app/views/homes/mypage.html.erb
<table>
  <caption>ブックマーク一覧</caption>
  <thead>
    <tr>
      <th>投稿者名</th>
      <th>タイトル</th>
      <th>本文</th>
    </tr>
  </thead>
  <tbody>
    <% @bookmarks.each do |bookmark| %>
      <tr>
        <td><%= bookmark.post.user.name %></td>
        <td>
          <%= link_to post_path(bookmark.post) do %>
            <%= bookmark.post.title %>
          <% end %>
        </td>
        <td><%= bookmark.post.body %></td>
      </tr>
    <% end %>
  </tbody>
</table>
```

```rb:app/controllers/homes_controller.rb
def mypage
  @bookmarks = Bookmark.where(user_id: current_user.id)
end
```