非同期のBookmark機能にトライ

### モデルの作成

まずは rails g model Bookmark でBookmarkモデルを作成。
```
rails g model Bookmark
```
rails db:migrate　をするその前に、マイグレーションファイルを編集しましょう！

```ruby
class CreateBookmarks < ActiveRecord::Migration[6.1]
  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
```
user=Bookmarkしたユーザー
post=Bookmarkされた投稿

マイグレーションファイルを編集し終わったらrails db:migrateを忘れずに！
```
rails db:migrate
```
### アソシエーション

UserモデルのidやPostモデルのidと関連付けをしていきましょう！

#### ☆Bookmarkモデル

```ruby:app/models/bookmark.rb
class Bookmark < ApplicationRecord
  belongs_to :post
  belongs_to :user
end
```
#### ☆Postモデルにも関連付けが必要

```ruby:app/models/post.rb
class Post < ApplicationRecord
  belongs_to :user
  has_many :bookmarks, dependent: :destroy①
  
  def bookmarked_by?(user)②
    bookmarks.where(user_id: user).exists?
  end
end
```
①dependent: :destroyは親（ここでいう投稿=post)が削除されると、関連する子(bookmark)も全部消しますよーという記述。
②bookmarked_by?メソッドってなに？？
このメソッドで、引数で渡されたユーザーidがBookmarksテーブル内に存在（exists?）するかどうかを調べます。
存在していればtrue、存在していなければfalseを返すようにしています。その結果、
存在しない=「まだBookmarkしていない→createへ」
存在する=「すでにBookmark済み→destroyへ」の流れとなります。

#### ☆最後にUserモデルにも関連付け追加

```ruby:app/models/user.rb
class User < ApplicationRecord
   :
  has_many :posts, dependent: :destroy
  has_many :bookmarks, dependent: :destroy
end
```
これで各モデルへのアソシエーションの記述は終わりです！


### ルーティング

```ruby:routes.rb
resources :posts  do
      resource :bookmarks, only: [:create, :destroy]
end
```

さて次はルーティングに行きましょう。
「どの投稿をBookmarkしたのか」判別できないといけないため、posts コントローラーにネストします。
ここで「あれ、resourcesじゃなくてなぜBookmarkはresourceなの？」と思いませんでした？
resource、つまり単数形にすると「/:id」がURLにつかなくなります。
Bookmark機能は、１投稿に対して１ユーザーが１回しかBookmarkできませんよね？
つまりBookmark対象の投稿(post)idとユーザーidとがわかっていれば、
BookmarkのidはURLに含める必要がない、つまりparams[:id]を使わなくても良いため
resourceの単数形でURLに/:idを含めないようになっています。

つまりresourceは「それ自身のidが分からなくても、関連する他のモデルのidから特定できる」
という場合に使うことが多いと認識しておきましょう。

結果出来上がったルーティングはこうなります。

```ruby
post_bookmarks  DELETE /posts/:post_id/bookmarks(.:format)     bookmarks#destroy
                POST   /posts/:post_id/bookmarks(.:format)     bookmarks#create
```


### コントローラー

次にBookmarkのコントローラーを作成します。複数形で作成するのを忘れずに。

```
rails g controller bookmarks
```
コントローラーを作成したらアクションを定義しましょう。

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

  def create
    @post = Post.find(params[:post_id])①
    bookmark = current_user.bookmarks.new(post_id: @post.id)②
    bookmark.save
  end

  def destroy
    @post = Post.find(params[:post_id])①
    bookmark = current_user.bookmarks.find_by(post_id: @post.id)
    bookmark.destroy
  end
end
```
Bookmrakは専用のviewは必要ないため、アクションはcreateとdestroyのみでよいです。
①でBookmarkする投稿のidを(params[:post_id])で取得します。
②post_idにはルーティングに記載してある「:post_id」を指定し、
current_user（ログイン中のユーザー）がBookmarkした投稿がわかるようにしています。

### Viewの作成

```ruby:app/view/posts/show.html.erb
<% if @post.bookmarked_by?(current_user) %>①
    <%= link_to post_bookmarks_path(@post), method: :delete, remote: true do %>　②
    ★ <%= @post.bookmarks.count %> Bookmark解除
    <% end %>
<% else %>
    <%= link_to post_bookmarks_path(@post), method: :post, remote: true do %>
    ☆ <%= @post.bookmarks.count %> Bookmark
    <% end %>
<% end %>
```
さて、いよいよ大詰めです。
①のbookmarked_by?メソッドは、先ほどPostモデルで説明したメソッドです。
引数で(current_user)を渡すことで、ユーザーが存在していればtrue(destroyアクション)、
存在していなければfalse(createアクション)を返します。
②非同期通信にしたいので、remote: trueを入れることでjsファイルを探しに行ってくれます。

### 最後に非同期の実装

まずはjsファイルの作成が必要です。
bookmarksのviewの下にcreate.js.erbとdestroy.js.erbを作成しましょう。

```ruby:app/views/bookmarks/create.js.erb
$('#bookmarks_buttons_<%= @post.id %>').html("<%= j(render "bookmarks/bookmarks", post: @post) %>");
```
```ruby:app/views/bookmarks/destroy.js.erb
$('#bookmarks_buttons_<%= @post.id %>').html("<%= j(render "bookmarks/bookmarks", post: @post) %>");
```

#### 注意点

コードを書き終えたものの、最初は非同期にならないどころか、Bookmarkボタンを押しても反応しませんでした。
ちゃんと合っていると思うけどなー、なにがダメなんだろう。。としばらく考えきづいたことが！！

```Ruby
 def create
    @post = Post.find(params[:post_id])①
    bookmark = current_user.bookmarks.new(post_id: @post.id)②
    if bookmark.save
      redirect_to request.referer
    else
      redirect_to request.referer
    end
 end
```
最初はこんな風にコードを書いていたんですね。
このif以下の条件分岐を消して、redirect_toを削除、
bookmark.saveだけにすると非同期で反応しました！

これでBookmark機能が完成しました。