Class: RuboCop::Cop::DevDoc::Rails::EnumMustBeSymbolized

Inherits:
Base
  • Object
show all
Defined in:
lib/rubocop/cop/dev_doc/rails/enum_must_be_symbolized.rb

Overview

Every Rails ‘enum` declaration must be paired with `enum_symbolize` so the reader returns a symbol instead of Rails’ default string form.

## Rationale Strings and symbols are not equal in Ruby (‘:foo == ’foo’‘ is `false`). Rails’ ‘enum` macro defines a reader that returns the string form, so downstream comparisons like `record.status == :active` silently fail. Pairing `enum :status, …` with `enum_symbolize :status` overrides the reader to hand back a symbol, eliminating the footgun.

See ‘best_practices/backend/en/01a_defensive_programming.md` item 7.


class Reimbursement < ApplicationRecord
  enum :payment_status, { draft: 0, pending: 1, finalized: 2 }
end


class Reimbursement < ApplicationRecord
  enum :payment_status, { draft: 0, pending: 1, finalized: 2 }

  enum_symbolize :payment_status
end

‘enum_symbolize` accepts multiple names so several enums can be paired in one call:


enum_symbolize :payment_status, :finalize_intent

Examples:

# bad
enum :status, { active: 0, archived: 1 }

# good
enum :status, { active: 0, archived: 1 }
enum_symbolize :status

Constant Summary collapse

MSG =
'Pair `enum :%<name>s` with `enum_symbolize :%<name>s` so the reader returns a symbol ' \
'(see backend/01a_defensive_programming.md item 7).'.freeze

Instance Method Summary collapse

Instance Method Details

#on_class(node) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/rubocop/cop/dev_doc/rails/enum_must_be_symbolized.rb', line 54

def on_class(node)
  body = node.body
  return unless body

  statements = body.begin_type? ? body.children : [body]

  enum_decls = []
  symbolized = []

  statements.each do |stmt|
    next unless stmt.respond_to?(:send_type?) && stmt.send_type?

    if (name = enum_call(stmt))
      enum_decls << [name, stmt]
    elsif (args = enum_symbolize_call(stmt))
      symbolized.concat(args.select(&:sym_type?).map(&:value))
    end
  end

  enum_decls.each do |name, decl|
    next if symbolized.include?(name)

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