Boolean のカラムを生やす前に考えたいこと

5566 ワード

① それは何かのイベントではないか

イベントを表すものの場合は、boolean ではなく日時型で持った方がいい

例:

boolean datetime
done done_at
completed completed_at
deleted deleted_at
discarded discarded_at
archived archived_at
read read_at
clicked clicked_at
checked checked_at
approved approved_at
authorized authorized_at
confirmed confirmed_at
published published_at
  • その方が単純に情報量が増えるため
    • 最初 boolean にしてたけど後から時刻もほしくなるということはよくある。後から日時型のカラムに変えようと思っても、過去のイベントの日時は失われてしまっていて困る。今は日時が必要でないように感じても日時にしておく方がよい。
    • またアプリケーション的に使わなくても、データ分析のためにあると嬉しいことは多い。
  • コード的にも特に扱いにくくなることはない
    • 以下のようにして簡単に boolean と同じ感覚で扱うことができる。
class Post < ApplicationRecord
  scope :unpublished, -> { where(published_at: nil) }
  scope :published, -> { where.not(published_at: nil) }
  
  def published?
    !published_at.nil?
  end
end

イベントそのものを別のテーブルとして設計した方がいいこともある

例:

datetime 別テーブル
published_at PostPublication
archived_at EmailLetterArchive
clicked_at NotificationClick
approved_at ContractApproval
confirmed_at EmailAddressConfirmation
completed_at StepCompletion
deleted_at TodoItemDeletion
closed_at IssueClose
reopened_at IssueReopen
  • 利点:
    • そのイベントが複数回発生しうる場合、起きた事実を失わずにすべて記録できる
      • 例: issue の close, reopen
    • 日時以外の追加情報も記録できる
      • 行為者
      • その時点での対象や行為者の状態
      • etc.
        • Proposal に rejected / rejected_at を追加する代わりに、
        • ProposalRejection (proposal_id, reviewer_id, reason, note, created_at)
  • むしろ、データモデリングとしてはこちらの方が正道。
    • REST の思想やそれに基づいた Rails にもこちらの方が適合する。
      • リソースに対する PUT/PATCH ではなく、イベントの POST で操作する。
    • ここらへんの話は texta.fm のこのあたりのエピソードがとても参考になる。https://anchor.fm/textafm/episodes/3--Low-Code-Development-emr6k3

順番としては、まずは別テーブルにする設計を考えてみて、日時以外の付加情報を記録しておかなくてもよく、イベントが1回しか発生しないような場合は日時型のカラムで済ませる、という順番で考えるとよい。別テーブルにした場合は、JOIN が必要になるので、パフォーマンスとのトレードオフは発生する。なんでもかんでも別テーブルにした方がいいというわけではない。

② 上位の概念に名前がつけられないか

  • 最初は true/false の2値だと思っていても、後から第3、第4のケースが出てくる場合がある。
  • この場合は boolean ではなく、enum にする。
  • 上位の概念に名前がつけられないか考えてみるとよい。上位の概念を表す名前が見つかったら、他のケースも思いつきやすい。
    • public: true/false
    • visibility (private: 0, public: 1)
    • → 将来 invitees_only: 2 を作りたくなるかも
  • 典型的なもの
    • state
    • category
    • xxx_type
    • role
    • etc.
  • コードとしても、上位の概念に名前がついていた方がわかりやすくなる
    • switch 文で宣言的に書けるなど

例:

boolean enum
draft / published /archived state (draft: 0, published: 1, archived: 2)
private / public visibility (private: 0, invitees_only: 1, public: 2)
admin role (member: 0, admin: 1)