20
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Rails 5】Userの現在の年齢を生年月日から計算する方法と、ransackを使わずに年齢の範囲検索フォームの実装

Last updated at Posted at 2019-03-04

目標

・gem(ransack)を使わずに年齢の範囲検索フォームを作成する
・view上では年齢での検索だが、Usesテーブルにageカラムは作成していないので、birthdayカラムからユーザーを取ってくる

悩んだこと

・年齢で検索するのにもかかわらず、birthdayカラムからUserを探して取り出す方法

完成図

レイアウトは後で適当に調節するとして、
スクリーンショット 2019-03-04 18.24.13.png
とりあえずこんな感じ。

ポイント

今回肝になるのは
・生年月日から年齢の計算方法
・年齢の範囲から生年月日の範囲を求める方法

である。

生年月日から年齢の計算方法

現在と生年月日それぞれをyyyymmdd形式で表して、

(現在-生年月日)/10000

の値を求め、この値の整数部分が年齢となる。

例えば、現在が2019/03/04、生年月日が1990/12/25だったとすると、

(20190304-19901225)/10000
=28.9079

よって、28歳ということになる。

なぜこの式で良いのかが知りたい人は

生年月日から年齢を簡易計算する数式 - Qiita

に詳しく載っているので、参考にしてください。

年齢の範囲から生年月日の範囲を求める方法
上記のことの逆を考えれば良い。
つまり、例えば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

やっていることは単純だ。空白だったら、代表値として代わりに0150を引数に入れている。

終わりに

今回は本来はviewで用いるメソッドを主に記述するhelperにcontrollerで用いるメソッドを定義してしまっているので、ここのところはよしなに変更してくださると良いと思います。
懸念点としては、controllerはできる限りスッキリとさせなければならないが、今回はcontrollerがとてもごちゃごちゃとしている。少しでも可読性を上げるためにコメントを追加しているが、やはりcontrollerにしては少しむさっとしている。

美しいリファクタリングのアドバイスをいただけると幸いです。

20
16
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?