【Rails6.1】2種類のレイアウトを使ったブックマークを非同期にする方法

# はじめに
ブックマーク機能を作成する際に、詳細ページと商品一覧ページで違う形のボタンを使用することにしました。
この状態で非同期にするにはどうしたら良いんだろう…？と悩みながら進めたので、実際に使った方法を記録しておきます。

これが、商品詳細のブックマーク。横長のボタンになっています。

![2024-06-20ブックマーク2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3762011/06a17ce4-1a3b-4d21-e39c-1f009de43b65.png)

こっちが商品一覧のブックマーク。リボンのアイコンになっています。

![dc1875f3917c3b8010e0b6936264613e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3762011/ee8cf26c-97c0-3411-5e4a-f84b334f97b3.png)



# 今回のポイント
create.js.erbファイルの記述の中で、特定のアイテムのパスを含んでいるかどうかをチェックして条件に応じて内容を書き換えます。

↓ jsファイルの記述
```javascript:bookmarks/create.js.erb
<% if request.referrer.include?(item_path(@item)) %>
  $(".bookmark-btn").html("<%= j(render 'public/bookmarks/btn_text', item: @item) %>");
<% else %>
  $("#item_<%= @item.id %> .bookmark-btn").html("<%= j(render 'public/bookmarks/btn_ribbon', item: @item) %>");
<% end %>
```
調べたりチャットGPTに聞いたりしながら、やり方を模索していると、コントローラに記述する方法などもありました。

私が試した中ではjsファイルで条件分岐を記述する方法が、一番シンプルで分かりやすかったのでこの方法で実装しています。

# 実際の記述
ブックマーク（いいね）機能の詳しい記述方法については割愛します。
調べたら分かりやすい記事もいろいろ見つかるので、そちらの解説をご覧ください。
（jsファイルの以外は基本の記述とほぼ変わりません）

## ビュー
```html:items/show.html.erb（商品詳細ページ）
<div class="bookmark-btn">
  <%= render 'public/bookmarks/btn_text', item: @item %>
</div>
```

```html:bookmarks/_btn_text.html.erb（部分テンプレート）
<% if item.bookmark_by?(current_member) %>
  <%= link_to  bookmarks_path do %>
    ブックマーク済み <i class="fa-solid fa-bookmark"></i> <%= item.bookmarks.count %>
  <% end %>
<% else %>
  <%= link_to item_bookmarks_path(item), remote: true, method: :post do %>
    ブックマークする <i class="fa-regular fa-bookmark"></i> <% if item.bookmarks.count > 1 %> <%= item.bookmarks.count %><% end %>
  <% end %>
<% end %>
```
書き換える部分に<b>bookmark-btn</b>のclassを付与しています。

```html:items/_index.html.erb（商品一覧）
<div class="bookmark-btn col-1">
  <%= render 'public/bookmarks/btn_ribbon', item: item %>
</div>
```

```html:bookmarks/_btn_ribbon.html.erb（部分テンプレート）
<% if item.bookmark_by?(current_member) %>
  <%= link_to bookmarks_path do %>
    <i class="fa-solid fa-bookmark"></i>
  <% end %>
<% else %>
  <%= link_to item_bookmarks_path(item), remote: true, method: :post do %>
    <i class="fa-regular fa-bookmark"></i>
  <% end %>
<% end %>
```
商品一覧ページでも同じように、書き換える部分のclass名を<b>bookmark-btn</b>としています。

詳細ページと一覧ページどちらのボタンでも、<b>「item_bookmarks_path(item)」</b>の同じリンクで、bookmarks_controllerのcreateアクションへ飛ぶことになっています。
（ちなみに今回作成したサイトでは、ブックマーク一覧でのみブックマークを削除できる設定にしているので、商品詳細や商品一覧のブックマーク済はdestroyアクションではなくブックマーク一覧へリンクさせています。）


## コントローラ

```ruby:bookmarks_controller.rb
  def create
    @item = Item.find(params[:item_id])
    bookmark = current_member.bookmarks.new(item_id: @item.id)
    bookmark.save
  end

  def destroy
    item = Item.find(params[:item_id])
    bookmark = current_member.bookmarks.find_by(item_id: item.id)
    bookmark.destroy
    redirect_to request.referer
  end
```
こちらも基本の記述と変わりません。
createは非同期なのでリダイレクト先も設定せずに、そのままcreateアクションへ飛ぶようにしています。
（destroyは非同期にしていないのでredirect_to になっています。）

## jsファイル

```javascript:bookmarks/create.js.erb
<% if request.referrer.include?(item_path(@item)) %>
  $(".bookmark-btn").html("<%= j(render 'public/bookmarks/btn_text', item: @item) %>");
<% else %>
  $("#item_<%= @item.id %> .bookmark-btn").html("<%= j(render 'public/bookmarks/btn_ribbon', item: @item) %>");
<% end %>
```
:::note info
ここではif文で、
<b>request.referrer</b>：参照元URL（ユーザーが来た元のページ）に
<b>(item_path(@&#8203;item))</b>：showページへのパスが
<b>.include?</b>：含まれているかを判定しています。
:::


つまり、URLがshowページだったら
<b>render 'public/bookmarks/btn_text', item: @&#8203;item</b>に書き換えて

そうでなければ
<b>render 'public/bookmarks/btn_ribbon', item: @&#8203;item</b>に書き換えます。

これで、ページ別にボタンのレイアウトを非同期で入れ替えることに成功しました…！


## おまけ…ブックマーク一覧ページ

ここからは本題と外れます。余談です。
ブックマーク商品の一覧は、商品一覧と同じ部分テンプレートを使用できます。

```html:bookmarks/bookmark_list.html.erb
<%= render 'index', items: @items %>
```

```ruby:bookmarks_controller.rb
  def bookmark_list
    active_shops = Shop.where(is_active: true)
    bookmark_items = Bookmark.where(member_id: current_member.id)
    @items = Item.where(id: bookmark_items.pluck(:item_id))
                 .where(is_active: true, shop_id: active_shops).order(id: 'DESC').page(params[:page])
  end
```
個人的にコントローラの記述でちょっとだけややこしかったのは、
<b>@&#8203;items = Item.where(id: bookmark_items.pluck(:item_id))</b>の部分です。

:::note info
<b>bookmark_items.pluck(:item_id)</b>は、bookmark_itemsの中からitem_idカラムの値だけを抽出し、配列として返します
:::

このpluckがいまいち理解できていなかったのですが、Railsガイドを見ると、

https://railsguides.jp/v6.1/active_record_querying.html#pluck

>pluckは、1つのモデルで使われているテーブルから1つ以上のカラムを取得するクエリを送信するときに利用できます。引数としてカラム名のリストを与えると、指定したカラムの値の配列を、対応するデータ型で返します。

そしてこちらの記事でも、シンプルに説明されています。

https://qiita.com/k-o-u/items/31e4a2f9f5d2a3c7867f

ここでやっとpluckのことが分かりました。


# おわりに
ブックマークのレイアウトをページ毎に変える際は、なんとなく焦っていてチャットGPTにずっと聞き続けていたのですが、冷静になって考えればif文で分けるのもけっこう単純だしすぐに思いつきそうなものでした。

まとめてみると、なんだこんなものか。と思うのですが、考えている間はああでもないこうでもないとかなり頭を悩ませていました。

詰まったら一度冷静になって考えてみることが大事だなと実感しています。
（自分の謎すぎる試行錯誤も記録してあったのですが、解読するのに時間がかかりそうだったので、その記録を乗せるのは諦めました。）