チェックボックスで複数タグ ~タグ検索~

に公開

タグ検索までの手順
① 管理者側でタグを用意する
② ユーザー側の投稿フォームでタグ登録項目を追加する
③ チェックボックスによるタグ検索フォームを実装する

前回は
 ① 管理者側でタグを用意する
 ② ユーザー側の投稿フォームでタグ登録項目を追加する
について記述しました。

今回は
 ③ チェックボックスによるタグ検索フォームを実装する
について記述します。


タグ検索

ビュー (検索フォーム)

私はタグ検索のフォームを投稿一覧画面(posts/index)にこのように表示しています。

見た目は投稿フォームのものと同様です。
コードを確認しましょう。

posts/index.html.erb
<% 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 %>

コントローラ

searches_controller.rb
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(検索対象のタグ)と比較。検索対象のタグ全てを含む投稿のみを検索結果として返す。

(ここはもう少し理解が必要だ...)

ビュー (検索結果一覧)

search.html.erb
 <h2 class="title m-3"><i class="fas fa-search"></i> "<%= @tag_word %>" 検索結果 <small>(<%= @posts_count.length %>件)</small></h2>

これで検索結果については以下のように表示されます。


投稿一覧表示についてはこれ以降にコードの記述が必要ですが、各々表示したいように記述が必要です。

以上です。

Discussion