今日から始めるrubyメタプログラミングもどき😋
何をしたいか
例えばUsersモデルを作ったとして、そいつが名前、電話番号、メールアドレスを持っていたとします。そしてわしはそのカラムたちを管理画面からlike検索をしたいんです
# == 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使ってちょちょいのちょいです
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と名付けてみました。
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モデル
側は下記のようになります。
class User < ApplicationRecord
include Searchable
define_like_search :name, :tel, :email
end
う〜ん美しい!!✨
これだけで name_like
tel_like
email_like
という3つのメソッドが生成されるようになりました。
こうしておけば、例えば後からAdminモデル
で名前とコメントでlike検索したいとなったら下記を追記するだけでOKになるんですね!
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が好きになりました🤗