目標
・gem(ransack
)を使わずに年齢の範囲検索フォームを作成する
・view上では年齢での検索だが、Usesテーブルにageカラムは作成していないので、birthdayカラムからユーザーを取ってくる
悩んだこと
・年齢で検索するのにもかかわらず、birthdayカラムからUserを探して取り出す方法
完成図
レイアウトは後で適当に調節するとして、
とりあえずこんな感じ。
ポイント
今回肝になるのは
・生年月日から年齢の計算方法
・年齢の範囲から生年月日の範囲を求める方法
である。
生年月日から年齢の計算方法は
現在と生年月日それぞれをyyyymmdd形式で表して、
(現在-生年月日)/10000
の値を求め、この値の整数部分が年齢となる。
例えば、現在が2019/03/04
、生年月日が1990/12/25
だったとすると、
(20190304-19901225)/10000
=28.9079
よって、28歳ということになる。
なぜこの式で良いのかが知りたい人は
に詳しく載っているので、参考にしてください。
年齢の範囲から生年月日の範囲を求める方法は
上記のことの逆を考えれば良い。
つまり、例えば20歳である人は現在と生年月日との差が
20 × 100000 ~ 20 × 10000 + 9999
であれば良いことが考えられる。
ということはもしも、20~30歳を検索しようと思ったのならば、現在と生年月日との差が
200000~309999
になる生年月日を見つけてくれば良い、ということだ。
実装
まず、検索フォーム
index.html.haml
= form_tag(users_serch_path, method: :get) do
%p 年齢
= text_field_tag :search_min_age
%p 〜
= text_field_tag :search_max_age
%br
= submit_tag '検索', name: nil
ここでのusers_serch_path
の部分はよしなに自分の表示させたいviewのpathを書いてください。
users_helper.rb
module UsersHelper
# 渡された年齢に本日なったばかりの生年月日をyyyymmdd形式で出力
def calc_younger_birthday(age)
Date.today.strftime("%Y%m%d").to_i - age.to_i * 10000
end
# 渡された年齢であるギリギリの生年月日をyyyymmdd形式で出力
def calc_older_birthday(age)
Date.today.strftime("%Y%m%d").to_i - age.to_i * 10000 - 9999
end
end
ここでは、controllerをスッキリさせるためにメソッドを作成しています。引数のage
はparamsの値を入れる予定なので、計算を実装するために文字列を数値へと変換してくれるto_i
メソッドを使用しています。
users_controller.rb
class UsersController < ApplicationController
include UsersHelper
def search
# 指定された年齢となる生年月日をyyyymmdd形式の文字列へと変換
younger_birth_ymd = calc_younger_birthday(params[:search_min_age]).to_s
older_birth_ymd = calc_older_birthday(params[:search_max_age]).to_s
# yyyymmdd形式の生年月日を日付形式に変換
younger_birthday = Time.parse(younger_birth_ymd)
older_birthday = Time.parse(older_birth_ymd)
# 条件に当てはまるUserを検索
@users = User.where(birthday: older_birthday..younger_birthday)
end
end
ここでは、先ほどのhelperに書いたメソッドと、数値を文字列に変換するto_s
メソッドを用いて、与えられた年齢の範囲での生年月日のうち、2つのローカル変数である、もっとも最近の場合(younger_birthday
)と昔の場合(older_birthday
)それぞれを引数に代入している。
そうして、これらの値をTime.parse()
によって、日付形式に戻し、where
でbirthdayカラムの値が条件の範囲に当てはまるUserを見つけられる。
あとは@users.each do |user|
などを用いてviewに出力すれば検索結果を表示できる。
少し改良
このままでも問題はないが、〜以上
や〜以下
のようにして検索しようとした時に、片方だけに値を入れて検索するということもよくあるだろう。そんな時のために条件分岐を書くことをオススメする。コードは以下のようだ。
users_controller.rb
def search
unless params[:search_min_age] == ""
# 指定された年齢となる生年月日をyyyymmdd形式の文字列へと変換
younger_birth_ymd = calc_min_birthday(params[:search_min_age]).to_s
else
# 最小の年齢として0歳にした
younger_birth_ymd = calc_min_birthday("0").to_s
end
unless params[:search_max_age] == ""
older_birth_ymd = calc_max_birthday(params[:search_max_age]).to_s
else
# 最大の年齢として十分な150歳にした
older_birth_ymd = calc_min_birthday("150").to_s
end
# yyyymmdd形式の生年月日を日付形式に変換
younger_birthday = Time.parse(younger_birth_ymd)
older_birthday = Time.parse(older_birth_ymd)
# 条件に当てはまるUserを検索
@users = User.where(birthday: older_birthday..younger_birthday)
end
やっていることは単純だ。空白だったら、代表値として代わりに0
か150
を引数に入れている。
終わりに
今回は本来はviewで用いるメソッドを主に記述するhelperにcontrollerで用いるメソッドを定義してしまっているので、ここのところはよしなに変更してくださると良いと思います。
懸念点としては、controllerはできる限りスッキリとさせなければならないが、今回はcontrollerがとてもごちゃごちゃとしている。少しでも可読性を上げるためにコメントを追加しているが、やはりcontrollerにしては少しむさっとしている。
美しいリファクタリングのアドバイスをいただけると幸いです。