👀
Boolean のカラムを生やす前に考えたいこと
① それは何かのイベントではないか
イベントを表すものの場合は、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
- REST の思想やそれに基づいた Rails にもこちらの方が適合する。
順番としては、まずは別テーブルにする設計を考えてみて、日時以外の付加情報を記録しておかなくてもよく、イベントが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) |
Discussion