2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】複雑な検索機能について

Posted at

記事概要

Ruby on Railsに複雑な検索機能を実装する方法について、まとめる

前提

  • Ruby on Railsでアプリケーションを作成している
  • 投稿機能を実装済みである

サンプルアプリ(GitHub)

想定する機能イメージ

様々な条件で商品の検索をかけられる
Image from Gyazo

ransack(Gem)

検索機能の実装を実現できるGem

オプション

検索時に使用できるオプション

オプション名 役割
cont (contain) 部分一致
eq (equal) 完全一致
in 複数の値(配列)のうちどれかと一致する
gteq (greater than or equal to) 以上
lteq (less than or equal to) 以下
関連先モデル名_カラム名_オプション 関連モデルのカラム名で検索をする

メソッド

search_form_forメソッド

ransackのGemで用意されている、検索フォームを実装する際に使用するメソッド
form_withのransackバージョンだと思うとイメージしやすい

<%= search_form_for @q, url: search_items_path, method: :get do |f| %>
  <%= f.label :name_cont, '商品名' %>
  <%= f.submit "検索" %>
<% end %>

手順1(Gemの導入)

  1. ransackのGemを導入する
    詳細は、こちらを参照

手順2(ルーティングの設定)

  1. 検索ページのルーティングを設定する
    config/routes.rb
    Rails.application.routes.draw do
      devise_for :users
      root "items#index"
      resources :items, only: [:new, :create, :show, :edit, :update] do
        # 検索用ページのルーティング
        collection do
          get 'search'
        end
      end
    end
    

手順3(コントローラーの設定)

  1. コントローラーにsearchアクションを追記する
    app/controllers/items_controller.rb
    # 省略
    
    def search
      # ransackにて検索オブジェクトを生成し、ransackを使用したフォームから送られてくるパラメーターを受け取る
      @q = Item.ransack(params[:q])
      # 検索結果を取得
      @items = @q.result
    end
    
    private
    
    # 省略
    
    記述 意味
    ransack 検索オブジェクトを生成する
    params[:q] ransackを使用したフォームから送られてくるパラメーターを受け取る
    result 検索結果を取得する

手順4(検索ページへの動線を設定)

  1. トップページに検索ページへのリンクを設けるため、app/views/items/index.html.erbを編集する
    <%= render "shared/header" %>
    <h3>トップページ</h3>
    <h3><%= link_to '商品出品', new_item_path%></h3>
    
    <%#=検索ページへのリンク %>
    <h3><%= link_to '商品検索', search_items_path%></h3>
    
    <% @items.each do |item| %>
      <div class="posted-content">
        <%= image_tag item.image %><br>
        <%= item.name%><br>
        <%= link_to '詳細', item_path(item.id)%>
      </div>
    <% end %>
    

手順5(データ取り込み)

  1. seedファイルからデータを取り込むため、seedファイルを編集する
    db/seeds.rb
    test_user_1 = User.create(nickname: "太郎", email: "taro@taro", password: "tarotaro")
    test_user_2 = User.create(nickname: "花子", email: "hanako@hanako", password: "hanakohanako")
    
    item_1 = Item.new(
     name: "スマートフォン",
     category_id: 7,
     price: 50000,
     user_id: test_user_1.id
     )
    item_1.image.attach(io: File.open(Rails.root.join("./app/assets/images/smartphone.png")), filename: 'smartphone.png')
    item_1.save
    
    item_2 = Item.new(
     name: "子供服",
     category_id: 3,
     price: 1000,
     user_id: test_user_2.id
     )
    item_2.image.attach(io: File.open(Rails.root.join("./app/assets/images/clothes.png")), filename: 'clothes.png')
    item_2.save
    
    item_3 = Item.create(
     name: "ドライヤー",
     category_id: 7,
     price: 3000,
     user_id: test_user_2.id
     )
    item_3.image.attach(io: File.open(Rails.root.join("./app/assets/images/hairdryer.png")), filename: 'hairdryer.png')
    item_3.save
    
  2. seedファイルを実行する
    % rails db:seed
    
  3. データ登録できたことを、Sequel Aceで確認する

手順6(商品名で検索できるように実装する)

  1. 検索フォームの大枠を作成するため、app/views/items/search.html.erbを編集する
    <%= render "shared/header" %>
    <h3>検索ページ</h3>
    <h3><%= link_to 'トップページへ戻る', root_path%></h3>
    <div class='item-contents' id="detailed-search-result-wrapper">
      
      <%#= 検索フォーム %>
      <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
        <%= f.submit '検索' %>
      <% end %>
      
      <%#= 省略 >
    
  2. 商品名での検索欄を作成する
    <%#= 省略 >
    
    <%#= 検索フォーム %>
    <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
      <div class="search-field">
        <%= f.label :name_cont, '商品名' %>
        <br>
        <%= f.text_field :name_cont, placeholder: '商品名' %>
      </div>
      <%= f.submit '検索' %>
    <% end %>
    
    <%#= 省略 >
    
  3. パスワードなどの秘匿情報が不正に検索されてしまうのを防ぐため、検索対象のカラムを指定する
    app/models/item.rb
      # 省略
      
      # ransackでの検索対象を指定
      def self.ransackable_attributes(auth_object = nil)
        ["name", "price", "category_id"]
      end
    end
    
  4. ブラウザにて、商品名で検索できることを確認する
    Image from Gyazo

手順7(カテゴリでも検索できるように実装する)

  1. カテゴリでの検索欄を作成するため、app/views/items/search.html.erbを編集する
    <%#= 省略 >
    
    <%#= 検索フォーム %>
    <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
      <div class="search-field">
        <%= f.label :name_cont, '商品名' %>
        <br>
        <%= f.text_field :name_cont, placeholder: '商品名' %>
      </div>
      <div class="search-field">
        <%= f.label :category_id_eq, 'カテゴリ' %>
        <br>
        <%= f.collection_select(:category_id_eq, Category.all, :id, :name, {include_blank: "---"}) %>
      </div>
      <%= f.submit '検索' %>
    <% end %>
    
    <%#= 省略 >
    
    <%= f.collection_select 第一引数, 第二引数, 第三引数, 第四引数, 第五引数 %>
    引数 役割 今回の値
    第一引数
    (メソッド名)
    ・カラム名
    ・name属性やid属性を決める
    :category_id_eq
    第二引数
    (オブジェクト)
    配列データを指定する
    (今回はカテゴリーデータの配列)
    Category.all
    第三引数
    (value)
    表示する際に参照するDBのカラム名 id
    第四引数
    (name)
    実際に表示されるカラム名 name
    第五引数
    (オプション)
    何も選択していない時に表示される内容
    (今回は「---」)
    include_blank
  2. ブラウザにて、カテゴリで検索できることを確認する
    Image from Gyazo

手順8(価格でも検索できるように実装する)

  1. 価格での検索欄を作成するため、app/views/items/search.html.erbを編集する
    <%#= 検索フォーム %>
    <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
      <div class="search-field">
        <%= f.label :name_cont, '商品名' %>
        <br>
        <%= f.text_field :name_cont, placeholder: '商品名' %>
      </div>
      <div class="search-field">
        <%= f.label :category_id_eq, 'カテゴリ' %>
        <br>
        <%= f.collection_select(:category_id_eq, Category.all, :id, :name, {include_blank: "---"}) %>
      </div>
      <div class="search-field">
        <%= f.label :price_gteq, '価格'%>
        <br>
        <%= f.number_field :price_gteq %> 円以上
        <br>
        <%= f.number_field :price_lteq %> 円以下
      </div>
      <%= f.submit '検索' %>
    <% end %>
    
  2. ブラウザにて、価格で検索できることを確認する
    Image from Gyazo

手順9(カテゴリの検索欄をチェックボックスにする)

  1. カテゴリーの検索欄をチェックボックスにするため、app/views/items/search.html.erbを編集する
    <%#= 検索フォーム %>
    <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
      <div class="search-field">
        <%= f.label :name_cont, '商品名' %>
        <br>
        <%= f.text_field :name_cont, placeholder: '商品名' %>
      </div>
      <div class="search-field">
        <%= f.label :category_id_in, 'カテゴリ' %>
        <br>
        <%= f.collection_check_boxes(:category_id_in, Category.all, :id, :name) %>
      </div>
      <div class="search-field">
        <%= f.label :price_gteq, '価格'%>
        <br>
        <%= f.number_field :price_gteq %> 円以上
        <br>
        <%= f.number_field :price_lteq %> 円以下
      </div>
      <%= f.submit '検索' %>
    <% end %>
    
  2. 複数カテゴリーを選択して検索できることを確認する
    Image from Gyazo
  3. ターミナルを確認すると、配列でカテゴリーidが連携されていることが確認できる
    Image from Gyazo

手順10(出品者名でも検索できるように実装する)

  1. 出品者名での検索欄を作成するため、app/views/items/search.html.erbを編集する
    <%#= 検索フォーム %>
    <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
      <div class="search-field">
        <%= f.label :name_cont, '商品名' %>
        <br>
        <%= f.text_field :name_cont, placeholder: '商品名' %>
      </div>
      <div class="search-field">
        <%= f.label :user_nickname_cont, '出品者名' %>
        <br>
        <%= f.text_field :user_nickname_cont, placeholder: '出品者名' %>
      </div>
      <div class="search-field">
        <%= f.label :category_id_in, 'カテゴリ' %>
        <br>
        <%= f.collection_check_boxes(:category_id_in, Category.all, :id, :name) %>
      </div>
      <div class="search-field">
        <%= f.label :price_gteq, '価格'%>
        <br>
        <%= f.number_field :price_gteq %> 円以上
        <br>
        <%= f.number_field :price_lteq %> 円以下
      </div>
      <%= f.submit '検索' %>
    <% end %>
    
  2. ニックネームを検索対象に設定する
    1. Itemモデルに対して、Userモデルへのアソシエーションを検索対象とする設定を行う
      app/models/item.rb
        # 省略
        
        # Itemsテーブルと紐づいたUsersテーブルがransacの検索対象になる
        def self.ransackable_associations(auth_object = nil)
          ["user"]
        end
      end
      
    2. Userモデルに対して、nicknameカラムを検索対象とする設定を行う
      app/models/user.rb
        # 省略
        
        # Usersテーブルのnicknameカラムが検索対象に含まれる
        def self.ransackable_attributes(auth_object = nil)
          ["nickname"]
        end
      end
      
  3. ブラウザにて、出品者名で検索できることを確認する
    Image from Gyazo

手順11(複数の商品名でも検索できるように実装する)

「カバン 子供」と入力した場合、「カバン 子供」という1つの文字列が商品名に含まれている商品を探してしまうため、何も検索に引っかからない。OR検索できるように実装する

Image from Gyazo

  1. 商品名での検索欄を修正するため、app/views/items/search.html.erbを編集する。_contのオプションを削除する
    <%#= 省略 %>
    
    <%#= 検索フォーム %>
    <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
      <div class="search-field">
        <%= f.label :name, '商品名' %>
        <br>
        <%= f.text_field :name, placeholder: '商品名' %>
      </div>
      
      <%#= 省略 %>
    
  2. 条件に当てはまる場合はパラメーターを修正するように記述する
    app/controllers/items_controller.rb
    # 省略
    
    def search
      # params[:q]がnilではない且つ、params[:q][:name]がnilではないとき(商品名の欄が入力されているとき)
      if params[:q]&.dig(:name)
        # squishメソッドで余分なスペースを削除する
        squished_keywords = params[:q][:name].squish
        # 半角スペースを区切り文字として配列を生成し、paramsに入れる
        params[:q][:name_cont_any] = squished_keywords.split(" ")
      end
      
      # ransackにて検索オブジェクトを生成し、ransackを使用したフォームから送られてくるパラメーターを受け取る
      @q = Item.ransack(params[:q])
      # 検索結果を取得
      @items = @q.result
    end
    
    # 省略
    
  3. ブラウザにて、複数の商品名での検索ができることを確認する
    Image from Gyazo
  4. 商品名の入力値が保存されるようにするため、app/views/items/search.html.erbを編集する
    <%#= 検索フォーム %>
    <%= search_form_for @q, url: search_items_path, html: {id: "detailed-search-form"} do |f| %>
      <div class="search-field">
        <%= f.label :name, '商品名' %>
        <br>
        <%= f.text_field :name, placeholder: '商品名', value: params[:q]&.dig(:name) %>
      </div>
    
  5. ブラウザにて、検索後も商品名の欄が空にならないことを確認する
    Image from Gyazo

手順12(ヘッダーの検索フォームから商品名検索をできるようにする)

  1. 商品名で検索できるようにするため、app/views/shared/_header.html.erbを編集する
    <header class='top-page-header'>
      <div class='search-bar-contents'>
        <%= search_form_for Item.ransack(params[:q]), url: search_items_path, html: {class: "search-form"} do |f| %>
          <%= f.text_field :name, placeholder: '商品名から探す', class: 'input-box' %>
          <br>
          <%= f.label :submit, class: "search-button" do %>
            <input type="submit" value="検索" class="send">
            <%= f.submit :submit, id: "q_submit", style: "display: none;"%>
          <% end %>
        <% end %>
      </div>
      <!-- 省略 -->
    
    • search_form_forの引数に、Item.ransack(params[:q])をセットしている理由
      • _header.html.erbは様々なページで使用されており、@qを使用する場合、その都度@qを定義しなくてはならない
  2. 検索ページ同様、複数の商品名での検索ができることを確認する
    Image from Gyazo
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?