Ruby on Rails 非同期通信(ajax)についての振り返り

カリキュラム学習・ポートフォリオ作成中、この機能を実装するのに苦戦したので、メモ。
非同期通信にしたかったのは以下の二点。

①特定の商品をお気に入り登録or削除する
![お気に入りの作成。削除ボタン.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/623946/584c56db-19ee-60ed-dd58-7fc1c66b2c9f.gif)


②お気に入り一覧から削除する
![お気に入り一覧の削除ボタン.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/623946/a60f4dfd-bbc6-2a71-32e3-430b3440ee37.gif)

# 前提条件
## テーブル
```ruby:schema.rb

  create_table "users", force: :cascade do |t|
(略)
  end
 create_table "products", force: :cascade do |t|
(略)
  end

  create_table "favorites", force: :cascade do |t|
    t.integer "user_id", null: false
    t.integer "product_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
```

## モデル(アソシエーション)
```ruby:favorite.rb
belongs_to :user
belongs_to :product

```


```ruby:user.rb
has_many :favorites, dependent: :destroy
```

```product.rb
has_many :favorites, dependent: :destroy

def favorited_by?(user)
  Favorite.where(user_id: user.id).exists?
end
```
## ルート

```ruby:rutes.rb
resources :products do
 resource :favorites, only: [:create, :destroy]
end
```

# 実装①(商品のお気に入り追加・削除)
productsコントローラは以下の通り

```ruby:products_controller.rb
def show
  @product = Product.find(params[:id])
end
```

次にお気に入りボタンをパーシャル化。(userディレクトリ内に作成)

```ruby:views/users/products/_favorite_button.html.erb
<% if product.favorites.where(user_id: current_user.id).exists? %>
  <%= link_to "お気に入りから削除",  users_product_favorites_path(product_id: product.id), method: :delete, remote: true %>
<% else %>
  <%= link_to "お気に入りに追加", users_product_favorites_path(product_id: product.id), method: :post, remote: true %>
<% end %>
```
ここで`remote: true`を付けることで、非同期通信が可能になる。

products/show.html.erbで呼び出し

```ruby:views/users/products/show.html.erb
<div id="favorites_buttons_<%= @product.id %>">
  <%= render 'users/products/favorite_button', product: @product %>
</div>
```

次はfavoritesのコントローラ。

```ruby:fovorites_controller.rb
  def create
    @product = Product.find(params[:product_id])
    favorite = current_user.favorites.new(product_id: @product.id)
    favorite.save
  end

  def destroy
    @product = Product.find(params[:product_id])
    @favorites = current_user.favorites
    favorite = current_user.favorites.find_by(product_id: @product.id)
    favorite.destroy
  end
```
ここでリダイレクト先を指定しなければ、JSの処理を探しに行ってくれる。
最後に作成・削除した場合のJS処理を作成する。

```ruby:views/users/favorites/create.js.erb
$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");
```

```ruby:views/users/favorites/destroy.js.erb
$("#favorites_buttons_<%= @product.id %>").html("<%= j(render 'users/products/favorite_button', product: @product) %>");
```
show.html.erb内の、idで設定した範囲のみが、処理後書き換えられるようになる。

# 実装②(お気に入り一覧から削除)

fovoritesのコントローラは以下の通り作成

```ruby:favorites_controller.rb
def index
  @favorites = current_user.favorites
end
```

非同期通信したい範囲をパーシャル化。

```ruby:view/users/favorites/_form.html.erb
<% if favorites.present? %>
  <table class="table">
    <thead>
      <tr>
        <th colspan="3">商品</th>
      </tr>
    </thead>
    <tbody>
      <% favorites.each do |f| %>
        <tr>
          <td>
            <%= attachment_image_tag f.product, :image, :fill, 80, 80, format: 'jpeg', fallback: "no_image.jpg", size: '80x80' %>
          </td>
          <td><%= link_to f.product.name, users_product_path(f.product.id) %></td>
          <td><%= link_to "お気に入りから削除",  users_product_favorites_path(product_id: f.product.id), method: :delete, remote: true %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
<% else %>
  <h3>
    お気に入りリストがありません。<br>
    商品ページから追加してみましょう
  </h3>
<% end %>
```

fovorites/index.html.erbで呼び出し

```ruby:
<div class="row">
  <div class="col-sm-6 offset-3" id="favorites_index">
    <%= render 'users/favorites/form', favorites: @favorites %>
  </div>
</div>
```

JSの処理に以下の１行を追加

```ruby:views/users/favorites/destroy.js.erb
$("#favorites_index").html("<%= j(render 'users/favorites/form', favorites: @favorites) %>");
```

これでお気に入り一覧にも非同期機能が実装できた。


# 実装した上での反省点・感想
- JSに記載しているインスタンス変数はそれぞれcreate・destroyメソッドから確認しているため、そっちでも定義してあげないといけないということが身にしみた。(考えてみれば当たり前だったが)
- 実装①についてはググればよく出てくるが、②は見かけないので自力で
解けて感動した。ただ流れを理解していれば迷うとこでも無かったなと感じた。