Class: RuboCop::Cop::DevDoc::Rails::EnumColumnNotNull

Inherits:
Base
  • Object
show all
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 only the positional `enum :name, …` form, and skips silently if the backing column cannot be found in the schema.

Examples:

# bad - the `status` column is nullable in db/schema.rb
class Order < ApplicationRecord
  enum :status, { active: 0, archived: 1 }
end

# good - the `status` column is `null: false` in db/schema.rb
class Order < ApplicationRecord
  enum :status, { active: 0, archived: 1 }
end

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].freeze

Instance Method Summary collapse

Instance Method Details

#on_send(node) ⇒ Object



66
67
68
69
70
71
# File 'lib/rubocop/cop/dev_doc/rails/enum_column_not_null.rb', line 66

def on_send(node)
  name = nullable_enum_name(node)
  return unless name

  add_offense(node, message: format(MSG, name: name))
end