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