Class: RuboCop::Cop::DevDoc::Migration::AvoidBooleanColumn
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Migration::AvoidBooleanColumn
- Defined in:
- lib/rubocop/cop/dev_doc/migration/avoid_boolean_column.rb
Overview
Avoid boolean columns; prefer an alternative that better models
the data.
Rationale
A boolean column collapses a domain into exactly two states, but
most "is X?" questions are richer than that in practice. The
constraint pushes developers past the path-of-least-resistance
add_column :boolean toward a shape that captures what the data
actually means.
The question to ask before reaching for t.boolean is:
"what does NULL mean here?" — and if the answer is "unset" or
"not yet decided", that semantic should be explicit in the
schema, not silently smuggled in as a third boolean state.
Consider the alternatives in order:
1. Timestamp — when the flag implies a moment in time
If the boolean answers "did X happen?", a timestamp gives both
the answer (approved_at.present?) AND the moment it happened
(approved_at), for free. A boolean gives only the first.
This pattern dominates the codebase (published_at, deleted_at,
archived_at, completed_at, etc.) for good reason.
2. Enum — when "unset" is a meaningful state, or a third
value is plausible
An enum with an explicit "no_X_needed" value models "unset"
honestly. NULL is outside an enum's domain (a type violation),
so the column can carry null: false without controversy — see
DevDoc/Rails/EnumColumnNotNull.
3. Model method — when the flag is derived from other data
If the answer can be computed from existing columns or associations, do not store it. A method keeps the source of truth singular.
4. Boolean — only when none of the above apply
A genuine binary preference with no meaningful third state
(e.g. auto_renew, communication_allowed). Inline-disable
this cop with a brief -- boolean reason. The column MUST also
carry null: false — enforced by the sibling cop
DevDoc/Migration/BooleanColumnNotNull.
NOTE: This cop catches t.boolean, add_column ..., :boolean,
and change_column ..., :boolean (a type change to boolean).
change_column_type migrations to a non-boolean type are not
flagged — that is the cleanup direction.
NOTE: This cop is deliberately NOT timestamp-aware. It cannot
tell whether a :datetime column would have been a better fit;
that judgment is the developer's at review time. The role of
this cop is to force the moment of judgment, not to make it.
Constant Summary collapse
- MSG =
'Avoid `boolean` columns; consider a timestamp (approved_at), enum, or model method. ' \ 'If a boolean is genuinely the right shape, disable this cop with a brief `-- boolean` reason ' \ 'and ensure the column carries `null: false` (see DevDoc/Migration/BooleanColumnNotNull).'.freeze
Instance Method Summary collapse
Instance Method Details
#on_send(node) ⇒ Object
126 127 128 129 130 |
# File 'lib/rubocop/cop/dev_doc/migration/avoid_boolean_column.rb', line 126 def on_send(node) return unless boolean_column?(node) || add_column_boolean?(node) || change_column_to_boolean?(node) add_offense(node) end |