Class: RuboCop::Cop::DevDoc::Rails::EnumColumnNotNull
- Inherits:
-
Base
- Object
- Base
- RuboCop::Cop::DevDoc::Rails::EnumColumnNotNull
- Includes:
- ActiveRecordHelper
- Defined in:
- lib/rubocop/cop/dev_doc/rails/enum_column_not_null.rb
Overview
Enum columns must be backed by a null: false database column.
Rationale
null: false is reserved for cases where NULL has no meaningful
interpretation — and an enum is the clearest such case. NULL is
outside the enum's domain (a type violation, not one of the defined
values), and if "unset" is meaningful it should be modeled as an
explicit enum value, never as NULL.
The line is drawn here for standardization and non-subjectivity.
Whether a regular column should be present is a business decision
open to debate (could email become optional once phone signup
exists?), so it is left to model-layer judgment. An enum's
non-null-ness is objective — it does not depend on any business
decision — so it is enforced mechanically rather than argued
column-by-column.
This cop is the inverse of DevDoc/Migration/AvoidNonNull: that cop
runs on the migration and strips null: false from regular columns;
this cop runs on the model, reads db/schema.rb, and requires
null: false on the column backing each enum.
Interaction with AvoidNonNull
An enum is a plain integer column, so AvoidNonNull cannot tell it
apart from a regular integer and WILL flag the null: false you add
to satisfy this cop. Disable it on that migration with a brief -- enum
reason, so the migration is self-documenting:
# rubocop:disable DevDoc/Migration/AvoidNonNull -- enum
add_column :orders, :status, :integer, null: false
# rubocop:enable DevDoc/Migration/AvoidNonNull
NOTE: This cop reads db/schema.rb and does nothing if it is absent
(e.g. projects using structure.sql). It also relies on the schema
being current, resolves the table name by Rails convention (STI,
self.table_name overrides, or namespaced models may not resolve),
recognizes the positional enum :name, … form (and glib's
enum_symbolize; see below), and skips silently if the backing
column cannot be found in the schema.
Glib's enum_symbolize :name, { … } (which declares the enum itself)
is recognized too — its presence validation guards the app layer, but
this cop is about the DB-level guarantee, which validations cannot
provide (update_column, update_all, insert_all, raw SQL). The
legacy two-line form (enum :name, … + enum_symbolize :name) is
covered via its bare enum line.
Constant Summary collapse
- MSG =
'Enum column `%<name>s` should be `null: false` — NULL is outside an enum\'s domain. ' \ 'Model "unset" as an explicit enum value.'.freeze
- RESTRICT_ON_SEND =
%i[enum enum_symbolize].freeze
Instance Method Summary collapse
Instance Method Details
#on_send(node) ⇒ Object
84 85 86 87 88 89 |
# File 'lib/rubocop/cop/dev_doc/rails/enum_column_not_null.rb', line 84 def on_send(node) name = nullable_enum_name(node) return unless name add_offense(node, message: format(MSG, name: name)) end |