Edited at

かんたん!Railsでlike検索のscopeをconcernで作る

今日から始めるrubyメタプログラミングもどき😋


何をしたいか

例えばUsersモデルを作ったとして、そいつが名前、電話番号、メールアドレスを持っていたとします。そしてわしはそのカラムたちを管理画面からlike検索をしたいんです


user.rb

# == Schema Information

#
# Table name: users
#
# id :bigint(8) not null, primary key
# name :string(255) not null
# tel :string(255)
# email :string(255) not null
# created_at :datetime not null
# updated_at :datetime not null

powered by annotate


従来の方法

まぁ愚直にやるならすごく簡単ですよね。scope使ってちょちょいのちょいです


user.rb

class User < ApplicationRecord

scope :name_like, -> (query) { where('name LIKE ?', "%#{query}%") }
scope :tel_like, -> (query) { where('tel LIKE ?', "%#{query}%") }
scope :email_like, -> (query) { where('email LIKE ?', "%#{query}%") }
end

でもでもちょっと待って。

確かにこれくらいならまだ許容範囲だけど、今後like検索の対象が増えたら?もしかしてここにはウォール・マリアが築かれてしまうの…?

さらにUserモデルだけでなく、例えばAdminモデルがあったとしてそこでも似て非なるカラムでlike検索したくなったら?私はウォール・シーナも築いてしまえるの…?

そんなお悩みを解決してくれるのがみんな大好き concern ちゃんなのです。


Concernって?

新しいプロジェクトをrails newするとmodelディレクトリの配下にデフォルトconcernsディレクトリが作られます。concernとはそのディレクトリの下に置くべき複数modelに登場する共通処理のことです。DRYの思想に則り、共通処理を切り出すために存在します。むりやりViewで例えるのであれば、partialに値するものといえるでしょう。

実はこのconcernめちゃくちゃ便利で、OSSのコードを読んでいるとmodel配下だけとはいわずにcontroller配下やdecorator配下に設置されてることも多いです。


改善案

ということで使って行きましょうconcern!!

OSS見てるとconcernsモジュールの命名は **ableがメジャーっぽいのでそれに則ります。「〜を可能にする」のableですね。今回は検索を可能にするconcernということでsearchableと名付けてみました。


concerns/searchable.rb

module Searchable

extend ActiveSupport::Concern

class_methods do
def define_like_search(*columns)
columns.each do |column|
scope :"#{column}_like", -> (query) { where("#{column} LIKE ?", "%#{query}%") }
end
end
end
end


class_methods周辺の理解は下記の記事が非常に参考になりました

Rails の module ClassMethods がやっている事

肝心のメソッドの中身も書き方は色々あると思うのですが、私はモノグサで一行ですっきり書きたい!という思いが強かったので、モデルにおけるlike検索をしたいカラムを可変長引数として渡せばhoge_likeというscopeが定義されるようにしました。

これによりUserモデル側は下記のようになります。


user.rb

class User < ApplicationRecord

include Searchable
define_like_search :name, :tel, :email
end

う〜ん美しい!!✨

これだけで name_like tel_like email_like という3つのメソッドが生成されるようになりました。

こうしておけば、例えば後からAdminモデルで名前とコメントでlike検索したいとなったら下記を追記するだけでOKになるんですね!


admin.rb

class Admin < ApplicationRecord

include Searchable
define_like_search :name, :comment
end

使い方は最初に愚直に定義したscopeたちと同じです

[1] pry(main)> User.name_like 'ミカサ'

=> [#<User id: 3, name: "ミカサ アッカーマン", email: "mikasa@
ackerman.com"
, tel: nil, created_at: "2019-08-20 13:21:46", updated_at: "2019-09-03 17:56:57">]

いい感じに壁建築を防げたのではないでしょうか!

ちなみに進撃の巨人展おもしろかったです😆


2019/09/07追記

知人からツッコミがあったので。

このメソッドっていつ呼び出されるの?hoge_likeを実行する度に define_like_search まで走らんの?という質問を受けました。

このdefine_like_searchはrailsのmodel 初期読み込み時だけに実行されます。

これを確認してさらにconcernが好きになりました🤗