Ruby on Railsで複数のモデルが絡む検索画面の作り方


さてさて、今回もRuby on Railsネタです。

大抵の業務システムではたいていデータを検索する画面があります。そんな検索画面ですが、簡単な検索条件であればいいのですが、

大抵は複数のモデルが絡み、アソシエーション先の項目を検索条件にしたい場合など、複雑になるケースがあります。

Railsやり始めの頃、かなりハマりました。

複数のモデルからなるデータを検索する際、そもそもどこに検索条件ロジックを書くのか、controllerにロジックを書くのは微妙だし、

かと言って一つのモデルにメソッドを定義して書くのもなんだかな〜。なんて思ったりしていました。じゃあどうすんのって感じなのですが、

なかなかそういった情報はネットには出ていないのが現状です。当初はransackというgemを入れて開発を進めていたのですが、

ちょっと複雑な検索条件をつくろうとすると、うまく使うことが出来ず、結局やめました。

でたどり着いた先が以下の方法です。ちなみにこれは「実践Ruby on Rails4」の本で書かれていた方法です。これが一番しっくり来る感じです。

例として、顧客情報一覧を表示する処理で、顧客ID,名前、TELで検索する画面を作成します。
モデルは
CustomerとAddressがあり、
customer has_one address といったアソシエーションになっています。
telはaddressモデルのフィールドとします。

まずは、formクラス用意します。
このクラスは
formsフォルダをapp配下に作成し、その中にformクラスを作成します。
そして、ActiveModelをincludeすることでModelと同じような動きをしてくれます。
検索条件に指定したい項目はattr_accessorに指定します。

app/forms
customer_search_form.rb

class CustomerSearchForm
  include ActiveModel::Model

  attr_accessor  :id, :name, :tel

  def search
    rel = Customer

    rel = rel.where(id: id) if id.present?

    rel = rel.where(name: name) if name.present?

    rel = rel.joins(:address).where(“address.tel" => tel) if tel.present?

    rel
  end
end

続いてコントローラはこんな感じです。
先ほど作成したFormクラスをnewする際にパラメータを全てクラスに渡します。
そしてsearchメソッドを呼ぶだけです。

def index
  @search_form = CustomerSearchForm.new(params[:search])
  @customers = @search_form.search
end

viewはこんな感じで書きます。
ポイントはformのmethodをgetにするのがポイントです。

= form_for @search_form, as: 'search', url: :customers, html: {method: :get} do |f|

 = f.text_field :id
 = f.text_field :name
 = f.text_field :tel

  = f.submit "検索"

このようにすることで、自在に検索条件を作ることが出来ます。
複数のモデルを処理する場合、formクラスを作成する方法はなかなかいいプラクティスのようです。

この手法はこちらの本を参考にしています。
他にもrailsの設計方法について、色々と詳しく載っていますので、興味ある方は是非