Railsでtime型を扱うのに少々苦戦したので

グループウェアのタイムカード機能(時刻のみ記録)を実装する際、時刻の情報をデータベースに保存する際のデータ型をどうすれば良いのか悩みました。少しですが。

PostgreSQLでは日付時刻関連のデータ型はこちらのドキュメント にも記載されていますが、以下の通りです。

  • timestamp [without time zone] 日付・時刻(時間帯なし)
  • timestamp with time zone 日付・時刻、時間帯付き
  • date 日付(時刻なし)
  • time [without time zone] 時刻(日付なし)
  • time with time zone その日の時刻のみ、時間帯付き
  • interval 時間間隔

time with time zone などは詳細を理解できない部分もありますが、個人的な経験上データベースに保存する時刻はUTCのほうが良いと考えています。

よって、時刻を保存するのに次の2つのデータ型に絞ります。

  • timestamp [without time zone] 日付・時刻(時間帯なし)
  • time [without time zone] 時刻(日付なし)

悩んだのは日付ありにするか、なしにするか

タイムカードには当然日付の情報も必要です。上記2つのデータ型はドキュメントではどちらも8バイトとなっており、日付なしにしてもメモリサイズ上のメリットもなさそうです。

これもまた個人的な経験上ですが、日付情報はtimestamp型ではなく、date型で持たせた方がよいと考えています。

よって、日付は別個に日付の情報として保持し、時刻はtime型を利用してみることにしました。


ここで問題が発生。日付は不要なのでtime型としたのに、ActiveRecordのオブジェクトの属性として取得した際には、 日付情報も付加されて取得できてしまうことです。
例えばデータベースには 09:00 で登録されていても ActiveRecordのモデルでは 2000-01-01 09:00:00 UTC のような形式で取得されます。

これでは、せっかく時刻だけにしたのにフォーマットする必要が出てきます。

「フォーマットするだけ」ではあるのですが、使いどころによってはコードが冗長になってしまうこともあるのではないでしょうか。

結局、moduleを作成し、日付をセットしている属性名と同じメソッドを定義しました。また、使う予定はないのですが生のデータをそのまま取得できた方がよいかと思い、YAGNIの思想に反してフォーマットしていないデータをそのまま取得するメソッドも勉強のため実装してみることにしました。(言い訳)

ActiveRecordにread_attributeというメソッドがあり、これを利用することでフォーマットしていないデータを直接取得できます。

一部コードを抜粋すると下記の通りです。

def time_format_filter(*column_names, options)
  ・・・
  define_method "#{column_name}_org" do
    self.read_attribute(column_name)
  end

  define_method column_name do
    self[column_name].try(:strftime, options[:format]) || options[:default]
  end
  ・・・
end

今回のポイントとしては、read_attributeを利用することで、モデルから元の値を取得することできるという点ですね。