🦁

[投稿に複数Tag] 検索編・三項演算子[Ruby on Rails]

2024/07/13に公開

ずっと今回の山場になると分かっていたタグ検索機能..
自分のしたい検索機能を実装されてる記事がうまく見つけられず困りました。
ので自分が将来見返せるように今回も書いていきます🙂‍↔️
ちなみに今回は検索できるタグは1個にしました!余裕があれば複数検索実装します。


実装したいこと

・タグ検索フォームを作る。(今回は検索結果を表示するページのみ)
・投稿詳細のタグを押すとそのタグで検索ができる(検索結果を表示するページに遷移)
・検索結果を表示するページでも出力されたタグを押すと検索できる。

前提

・投稿モデルとタグモデルとその中間テーブルがありアソシエーション済み
・ルーティングができている
・post controllerにアクション名が記載済み

記載場所

1.post controller
2.post/tag_search.html.erb(検索結果を表示するページ)
3.post/show(投稿詳細のタグを押すとそのタグで検索ができるのため)

1.post controller

post controller
def tag_search
 @tag = Tag.find_by(name: params[:tag])
 @posts = @tag ? @tag.posts : Post.none
end

解説

@tag = Tag.find_by(name: params[:tag])

dbから名前が (検索窓に入力された)params[:tag] に一致するタグを探します。
一致するタグが見つかれば、そのタグのオブジェクトが @tag に代入されます。見つからなければ nil が代入されます。

@posts = @tag ? @tag.posts : Post.none

@tag が存在する場合(つまり、一致するタグが見つかった場合)、
そのタグに関連する投稿(@tag.posts)を @posts に代入します。
@tag が存在しない場合(つまり、一致するタグが見つからなかった場合)、
Post.none を @posts に代入します。Post.none は空のアクティブレコードリレーションを返します。

三項演算子の基本構文

@posts = @tag ? A(true) : B(false)

@tag が true の場合、A(true) が返され@postsに代入。
@tag が false の場合、B(false) が返され@postsに代入。

つまり今回は、@tag が存在するかどうか(検索窓にうった文字と同じ名前のタグが存在するか)に基づいて異なる値を @posts に代入しています。
A(true)(存在した場合)=そのタグがついてる投稿(@tag.posts)を代入
B(false)(存在しなかった場合)=Post.none 空だよってデータを代入

他のケースでの使用例

2.post/tag_search.html.erb(検索結果を表示するページ)

post/tag_search.html.erb内の検索窓
<%= form_with url: tag_search_posts_path, local: true, method: :get do |f| %>
  <%= f.text_field :tag, placeholder: "タグ検索" %>
  <%= f.submit "検索", class: "btn-sm btn-info" %>
<% end %>
post/tag_search.html.erbのタイトル
<%= @tag ? "##{@tag.name} の検索結果" : "タグが見つかりませんでした" %>

これもさっきと同じ'?'を使った三項演算子!!

@tagがtrue(存在する場合)なら "##{@tag.name} の検索結果"
@tagがfalse(存在しない場合)なら "タグが見つかりませんでした"

と返すよーってこと!!!!
今回はタグ名の前に'#'をつけたかったので##{@tag.name}と記載してます!
レイアウトの部分は消して書きます。

post/tag_search.html.erbのbody
# @posts が存在するかどうか(@posts = @tag ? @tag.posts : Post.noneの結果次第)
<% if @posts.present? %>

  <% @posts.each do |post| %>
 #出力された投稿がそのまま詳細ページへのリンクになるように。
  <%= link_to post_path(post) do %>
    <%= image_tag url_for(post.post_images.first) %>
        <h5><%= post.name %></h5>
    <p><%= post.address %></p>
  <% end %>

#タグがついてる投稿ならタグを出力しますよー
<% if post.tags.any? %>

 <% post.tags.each do |tag| %>
#これも出力されたタグがそのままタグ検索に使えるようにリンクで囲む
 <%= link_to tag_search_posts_path(tag: tag.name) do %>
 <%= tag.name %>
<% end %> 
<% end %> 

<% end %> #if post.tags.any?

<% end %> # @posts.eachのdo

<% else %>
# 存在しない場合
  <p>このタグに一致する投稿はありません。</p>
<% end %>
レイアウトありはこっち
<% if @posts.present? %>
  <div class="row">
    <% @posts.each do |post| %>
      <div class="col-4 mb-4">
        <div class="card h-100">
            
          <%= link_to post_path(post), class: "text-decoration-none text-dark" do %>
            <%= image_tag url_for(post.post_images.first), class: "card-img-top img-thumbnail", style: "height: 250px; object-fit: cover;" %>
            <div class="card-body">
              <h5 class="card-title"><%= post.name %></h5>
              <p class="card-text"><i class="fa-solid fa-location-dot fa-sm" style="color: #ff0f6f;"></i> <%= post.address %></p>
          <% end %>
              
              <% if post.tags.any? %>
                <div class="mt-2">
                  <% post.tags.each do |tag| %>
                   <%= link_to tag_search_posts_path(tag: tag.name), class: "badge bg-info", style: "color: white;" do %>
                    <span class="badge bg-info text-white">#<%= tag.name %></span>
                   <% end %>
                  <% end %>
                </div>
            </div>
            
          <% end %>
        </div>
      </div>
    <% end %>
  </div>
<% else %>
  <p>このタグに一致する投稿はありません。</p>
<% end %>

『沸いた疑問』

🧐 {これって極端な話全部@tag?で条件分岐してかけるってこと??
😎 {yes!!ただ複雑な条件分岐には向かないのと可読性的にも普通にif文使うのがおすすめ⭐️

post/show(投稿詳細のタグを押すとそのタグで検索ができる)

ここはさっきpost/tag_search.html.erb(検索結果を表示するページ)でもやったように
検索リンクで出力を囲ってあげるだけ!!!

<% @post.tags.each do |tag| %>
#<%= tag.name %>
<% end %>

から下記に変更。

<% @post.tags.each do |tag| %>
 <%= link_to tag_search_posts_path(tag: tag.name), class: "badge bg-info", style: "color: white;" do %>
  #<%= tag.name %>
<% end %>
<% end %>

感想

controllerは初めてやった書き方(三項演算子)だったので困惑したけど、そこが理解できたら後は理解しやすかった。特にずっっとやりたかった『タグを押したらそのまま検索結果に飛ぶ』っていうのは他がきちんと出来ていれば(controllerと結果ページ)linkで囲むだけでいいんだーと思いました😙

ただcontrokkerもifじゃダメなんか?とは思いましたね。
今回はせっかく知った新しい書き方を忘れるのは勿体無いので記事にしたけど、可読性のために

def tag_search
  @tag = Tag.find_by(name: params[:tag])

  #@posts = @tag ? @tag.posts : Post.none
  #下記と同じ

  if @tag
    @posts = @tag.posts
  else
    @posts = Post.none
  end
end

に書き直しました🤓(後から見た時絶対こっちのがわかりやすいもん)
タイトルのところはそのままにしておこっかなーー!!
なんやこれって思って見直せるように笑


実際に使ってみた

12/12
入社してJavaの環境で既存バグ修正中に「三項演算子でやってみよう!」となりこの記事を見直しました😳嬉

ただ1つのやり方しか知らないと頭が固いエンジニアになるから他のやり方もあると知っておこう!と言われ確かになぁ~~と!

三項演算子の他にnullの可能性があるときに使われる機能(オプショナル)

  1. StringUtills(クラスライブラリ)を使用したnull制限
    StringUtillsnullかつ空文字だったらtrueとしたい場合に使用する🥺
    例えば、StringUtils.isEmpty()やStringUtils.defaultIfEmpty()などがあります。
import org.apache.commons.lang3.StringUtils;

// 文字列がnullまたは空文字の場合にデフォルト値を使う
String clinicName = StringUtils.defaultIfEmpty(params.clinicName, "_");

これにより、clinicName がnullまたは空文字の場合に_を代入することができます。

[ StringUtills(クラスライブラリ)参考記事 ]
https://qiita.com/Mk-4000/items/a3db8457c1283de3fd3e

  1. Objectsを使用したnull制限
    Objects クラスは、Java 8で追加されたクラスで、nullチェックや比較を便利に行うために使用されます。
    ・Objects.isNull(params.clinicName)
    ・Objects.nonNull(params.clinicName)などがある
import java.util.Objects;

// nullチェック
if (Objects.isNull(params.clinicName)) {
    clinicName = "_";  // clinicNameがnullの場合は "_" を代入
} else {
    clinicName = params.clinicName;
}

// nullでない場合の処理
if (Objects.nonNull(params.clinicName)) {
    clinicName = params.clinicName;
} else {
    clinicName = "_";  // clinicNameがnullの場合は "_" を代入
}

[ Objects参考記事 ]
https://qiita.com/ry-s/items/ebf5249521c00b666070

Discussion