ransackなどのgemを使わずに、独自の検索ロジックを組んでいく

要件

Ruby on Railsのプロジェクトで検索機能といえば、gem ransackを使うことがあると思います。
gemは便利である一方、カスタマイズ性の調査に時間がかかったり、最悪ビジネス要件を満たせないケースもあります。
Railsでは絶対使うであろうgemがいくつかありますが、検索機能はロジックがわかれば自作した方が、自由に実装ができるため透明性の高いソースコードを作っていくことができます。
この記事ではそのTipsを記載したいと思います。

実装解説(Controller)

(1) supported_paramsには検索で使われるパラメータを制限しています

これらのパラメータを、引数も渡しながらメソッドチェーンしていきます。

(2) injectはブロックを使用して繰り返し処理を行います

第一引数のresultがinjectの初期値Userオブジェクトが入ります。第二引数のparamには、supported_paramsの要素が一つずつ入ります。

(3) パラメータの値があれば、レシーバresult(ここではUserオブジェクト)が持っているメソッドを、sendメソッドで呼び出します。

(4) パラメータの値がないか、レシーバがsupported_paramsで指定しているメソッドを持っていない場合は、レシーバ自身を返します。

supported_paramsの要素順に前回の戻り値とその要素の値がブロック引数に渡されて実行されます。

(5) 最後にpaginateのメソッドを別途チェーンします。

            
              class ResumesController < ApplicationController
                def search
                  @resumes =
                    supported_params.inject(User) do |result, param|
                      if params[param].present? and User.respond_to? "#{param}"
                        result.send(:"#{param}", params[param])
                      else
                        result
                      end
                    end
                    .page(params[:page])
                end

                private

                def supported_params
                  [
                    :ids_in,
                    :age_gteq,
                    :age_lteq,
                    :keyword_eq,
                  ]
                end
              end
            
          
つまり、全ての検索項目に入力があった場合、こうなるわけです。
検索パラメータがない場合は、該当行のメソッドチェーンはされません。
            
              User.ids_in(params[:ids_in])
                .age_gteq(params[:age_gteq])
                .age_lteq(params[:age_lteq])
                .keyword_eq(params[:keyword_eq])
                .page(params[:page])
            
          

実装解説(Model)

続いて、Userモデルで、supported_paramsで指定しているメソッドをそれぞれ見ていきましょう。
賛否はありますが、ここではarelを使用しています。
            
              class User < ApplicationRecord
                scope :ids_in, ->(ids) {
                  where(User.arel_table[:id].in ids)
                }
                scope :age_gteq, ->(age) {
                  joins(:profile)
                    .where(Profile.arel_table[:age].gteq age)
                }
                scope :age_lteq, ->(age) {
                  joins(:profile)
                    .where(Profile.arel_table[:age].lteq age)
                }
                scope :keyword_eq, ->(keyword) {
                  where(User.arel_table[:keyword].eq keyword)
                }
              end
            
          

まとめ

gemは便利ですが、カスタマイズ性の調査に時間がかかったり、最悪ビジネス要件を満たせないケースもあったりするため、必要なものだけを使うようにし、必要ないものは自作で実装できると、全体の保守性も上がるかなと思っています。
要件や予算、開発期間に合わせて、自由に設計できるといいですね。
国内外にチームがあり、費用感や品質、スピード、様々なオーダーに対して、
柔軟にカスタマイズしてチームを作ります。
まずはお気軽にお見積もり依頼からご連絡ください。
お見積もり依頼はこちら