module ActiveRecord::StringEnum

Declare an enum attribute where the values map to integers in the database, but can be queried by name. Example:

class Conversation < ActiveRecord::Base
  enum status: [ :active, :archived ]
end

# conversation.update! status: :active
conversation.active!
conversation.active? # => true
conversation.status  # => "active"

# conversation.update! status: :archived
conversation.archived!
conversation.archived? # => true
conversation.status    # => "archived"

# conversation.update! status: :archived
conversation.status = "archived"

# conversation.update! status: nil
conversation.status = nil
conversation.status.nil? # => true
conversation.status      # => nil

Scopes based on the allowed values of the enum field will be provided as well. With the above example:

Conversation.active
Conversation.archived

Of course, you can also query them directly if the scopes doesn’t fit your needs:

Conversation.where(status: [:active, :archived])
Conversation.where.not(status: :active)

You can set the default value from the database declaration, like:

create_table :conversations do |t|
  t.column :status, :string, default: :active
end

In rare circumstances you might need to access the mapping directly. The mappings are exposed through a class method with the pluralized attribute name.

Conversation.statuses[0] # => :active
Conversation.statuses[1] # => :archived

Constants

ENUM_CONFLICT_MESSAGE
VERSION

Public Instance Methods

str_enum(definitions) click to toggle source
# File lib/active_record/string_enum.rb, line 98
def str_enum(definitions)
  klass = self
  definitions.each do |name, values|
    # statuses = [ ]
    enum_values = values.map(&:to_s)
    name        = name.to_sym

    # def self.statuses statuses end
    detect_enum_conflict!(name, name.to_s.pluralize, true)
    klass.singleton_class.send(:define_method, name.to_s.pluralize) { values }

    detect_enum_conflict!(name, name)
    detect_enum_conflict!(name, "#{name}=")

    # TODO: in Rails 4.2.1 this will be legal:
    # attribute name, EnumType.new(name, enum_values)
    # instead of the next lines:
    type = EnumType.new(name, enum_values)
    define_method("#{name}=") do |value|
      write_attribute(name, type.cast(value))
    end

    define_method(name) { type.deserialize(self[name]) }

    _enum_methods_module.module_eval do
      enum_values.each do |value|
        # def active?() status == :active end
        klass.send(:detect_enum_conflict!, name, "#{value}?")
        define_method("#{value}?") { self[name] == value }

        # def active!() update! status: :active end
        klass.send(:detect_enum_conflict!, name, "#{value}!")
        define_method("#{value}!") { update! name => value }

        # scope :active, -> { where status: :active }
        klass.send(:detect_enum_conflict!, name, value, true)
        klass.scope value, -> { klass.where name => value }
      end
    end
    defined_str_enums[name.to_s] = enum_values
  end
end

Private Instance Methods

_enum_methods_module() click to toggle source
# File lib/active_record/string_enum.rb, line 142
def _enum_methods_module
  @_enum_methods_module ||= begin
    mod = Module.new
    include mod
    mod
  end
end
detect_enum_conflict!(enum_name, method_name, klass_method = false) click to toggle source
# File lib/active_record/string_enum.rb, line 155
def detect_enum_conflict!(enum_name, method_name, klass_method = false)
  if klass_method && dangerous_class_method?(method_name)
    raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
      enum: enum_name,
      klass: self.name,
      type: 'class',
      method: method_name,
      source: 'Active Record'
    }
  elsif !klass_method && dangerous_attribute_method?(method_name)
    raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
      enum: enum_name,
      klass: self.name,
      type: 'instance',
      method: method_name,
      source: 'Active Record'
    }
  elsif !klass_method && method_defined_within?(method_name, _enum_methods_module, Module)
    raise ArgumentError, ENUM_CONFLICT_MESSAGE % {
      enum: enum_name,
      klass: self.name,
      type: 'instance',
      method: method_name,
      source: 'another enum'
    }
  end
end