はじめに
ブックマーク機能を作成する際に、詳細ページと商品一覧ページで違う形のボタンを使用することにしました。
この状態で非同期にするにはどうしたら良いんだろう…?と悩みながら進めたので、実際に使った方法を記録しておきます。
これが、商品詳細のブックマーク。横長のボタンになっています。
こっちが商品一覧のブックマーク。リボンのアイコンになっています。
今回のポイント
create.js.erbファイルの記述の中で、特定のアイテムのパスを含んでいるかどうかをチェックして条件に応じて内容を書き換えます。
↓ jsファイルの記述
<% 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ファイルの以外は基本の記述とほぼ変わりません)
ビュー
<div class="bookmark-btn">
<%= render 'public/bookmarks/btn_text', item: @item %>
</div>
<% 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 %>
書き換える部分にbookmark-btnのclassを付与しています。
<div class="bookmark-btn col-1">
<%= render 'public/bookmarks/btn_ribbon', item: item %>
</div>
<% 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名をbookmark-btnとしています。
詳細ページと一覧ページどちらのボタンでも、「item_bookmarks_path(item)」の同じリンクで、bookmarks_controllerのcreateアクションへ飛ぶことになっています。
(ちなみに今回作成したサイトでは、ブックマーク一覧でのみブックマークを削除できる設定にしているので、商品詳細や商品一覧のブックマーク済はdestroyアクションではなくブックマーク一覧へリンクさせています。)
コントローラ
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ファイル
<% 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 %>
ここではif文で、
request.referrer:参照元URL(ユーザーが来た元のページ)に
(item_path(@item)):showページへのパスが
.include?:含まれているかを判定しています。
つまり、URLがshowページだったら
render 'public/bookmarks/btn_text', item: @itemに書き換えて
そうでなければ
render 'public/bookmarks/btn_ribbon', item: @itemに書き換えます。
これで、ページ別にボタンのレイアウトを非同期で入れ替えることに成功しました…!
おまけ…ブックマーク一覧ページ
ここからは本題と外れます。余談です。
ブックマーク商品の一覧は、商品一覧と同じ部分テンプレートを使用できます。
<%= render 'index', items: @items %>
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
個人的にコントローラの記述でちょっとだけややこしかったのは、
@items = Item.where(id: bookmark_items.pluck(:item_id))の部分です。
bookmark_items.pluck(:item_id)は、bookmark_itemsの中からitem_idカラムの値だけを抽出し、配列として返します
このpluckがいまいち理解できていなかったのですが、Railsガイドを見ると、
pluckは、1つのモデルで使われているテーブルから1つ以上のカラムを取得するクエリを送信するときに利用できます。引数としてカラム名のリストを与えると、指定したカラムの値の配列を、対応するデータ型で返します。
そしてこちらの記事でも、シンプルに説明されています。
ここでやっとpluckのことが分かりました。
おわりに
ブックマークのレイアウトをページ毎に変える際は、なんとなく焦っていてチャットGPTにずっと聞き続けていたのですが、冷静になって考えればif文で分けるのもけっこう単純だしすぐに思いつきそうなものでした。
まとめてみると、なんだこんなものか。と思うのですが、考えている間はああでもないこうでもないとかなり頭を悩ませていました。
詰まったら一度冷静になって考えてみることが大事だなと実感しています。
(自分の謎すぎる試行錯誤も記録してあったのですが、解読するのに時間がかかりそうだったので、その記録を乗せるのは諦めました。)