チェックボックスで複数タグ ~タグ検索~
タグ検索までの手順
① 管理者側でタグを用意する
② ユーザー側の投稿フォームでタグ登録項目を追加する
③ チェックボックスによるタグ検索フォームを実装する
前回は
① 管理者側でタグを用意する
② ユーザー側の投稿フォームでタグ登録項目を追加する
について記述しました。
今回は
③ チェックボックスによるタグ検索フォームを実装する
について記述します。
タグ検索
ビュー (検索フォーム)
私はタグ検索のフォームを投稿一覧画面(posts/index)にこのように表示しています。
見た目は投稿フォームのものと同様です。
コードを確認しましょう。
<% if member_signed_in? %>
<div class="col-md-5 my-3 p-3 border shadow-sm">
<div class="tag-search">
<%= form_with url: search_path, local: true, method: :get do |f| %>
<div class="d-flex flex-row flex-wrap">
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name do |b| %>
<div class="mx-1">
<%= b.check_box %>
<%= b.label %>
</div>
<% end %>
<%= button_tag type: "submit", class: "border rounded-pill shadow-sm search-btn" do %>
<i class="fas fa-search"></i>
<% end %>
</div>
<% end %>
</div>
</div>
<% end %>
<% if member_signed_in? %>
ユーザーがログインしている場合のみタグ検索が可能です。
<%= f.collection_check_boxes :tag_ids, Tag.all, :id, :name do |b| %>
チェックボックス部分の記述。この後<div>
で囲ってチェックボックスとタグ名をセットにしています。
検索実行ボタンは<%= f.submit "検索" %>
と書くところを以下のように記述すると、アイコン(FontAwesome使用)がボタンになります。
<%= button_tag type: "submit", class: "border rounded-pill shadow-sm search-btn" do %>
<i class="fas fa-search"></i>
<% end %>
コントローラ
def search
#タグ検索
@tag_ids = params[:tag_ids]&.select(&:present?)
if @tag_ids.present?
@tag_word = "タグ: "
@tag_ids.each do |id|
@tag_word = @tag_word + ' ' + Tag.find(id).name if id != ""
end
@posts = @posts.joins(:post_tags).where(post_tags: {tag_id: @tag_ids}).group("posts.id").having("count(*) = #{@tag_ids.length}")
end
# 検索結果件数
@posts_count = @posts
end
@tag_ids = params[:tag_ids]&.select(&:present?)
タグ検索で✔︎が付けられた要素を、インスタンス変数@tad_idsに代入します。
コードの詳細は、
params[:tag_ids]&.
&. は、結果がnil
の場合にエラー表示せずnil
を返すための演算子。
select(&:present?)
= select {|x| x.present?}
(直前のparams[:tag_ids] の)各要素に対し、presentメソッドで存在するかどうか確認し、存在する要素(nilでない要素)のみを返す。
-> 結果、@tag_ids
にはnilでないタグ要素(チェックボックスで✔︎がついた要素)のみが代入されることになります。
if @tag_ids.present?
タグ検索で✔︎をつけた要素がある場合、
どのタグで検索したか
@tag_ids.each do |id|
検索結果に、どのタグの検索結果なのか表示するための記述です。
タグは複数選択できるので、each文で一つずつ取得し "id" とします。
@tag_word = @tag_word + ' ' + Tag.find(id).name if id != ""
@tag_word は一つ上の行で "タグ:" と定義済ですが、これを再定義する記述です。
"id" に該当するタグ名を''スペースで区切って全て表示します。
if id != ""
これは結果表示に空を除外するif文です。ターミナルを確認すると、
Parameters: {"tag_ids"=>["", "1", "2", "3"], "button"=>""}
のように、tag_idsでtagのidを配列として取得していますが、先頭に""というidを含まない空の値があります。これを表示しないための記述です。
検索した全てのタグに該当する投稿全て
@posts = @posts.joins(:post_tags).where(post_tags: {tag_id: @tag_ids}).group("posts.id").having("count(*) = #{@tag_ids.length}")
.joins(:post_tags)
PostモデルとPostTagモデルを結合し、Postモデルのレコードに紐づくPostTagモデルのレコードを取得できる。
.where(post_tags: {tag_id: @tag_ids})
post_tagsテーブルから、✔︎を付けて検索対象としたタグのIDを持つレコードを検索する。
.group("posts.id")
PostモデルのIDをもとにレコードを一つのグループにまとめる。
.having("count(*) = #{@tag_ids.length}")
上記グループに含まれるPostTagのレコードを数え、@tag_ids(検索対象のタグ)と比較。検索対象のタグ全てを含む投稿のみを検索結果として返す。
(ここはもう少し理解が必要だ...)
ビュー (検索結果一覧)
<h2 class="title m-3"><i class="fas fa-search"></i> "<%= @tag_word %>" 検索結果 <small>(全<%= @posts_count.length %>件)</small></h2>
これで検索結果については以下のように表示されます。
投稿一覧表示についてはこれ以降にコードの記述が必要ですが、各々表示したいように記述が必要です。
以上です。
Discussion