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: 0
conversation.active!
conversation.active? # => true
conversation.status  # => "active"

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

# conversation.status = 1
conversation.status = "archived"

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.not_active
Conversation.archived
Conversation.not_archived

Of course, you can also query them directly if the scopes don'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, :integer, default: 0
end

Good practice is to let the first declared status be the default.

Finally, it's also possible to explicitly map the relation between attribute and database integer with a hash:

class Conversation < ActiveRecord::Base
  enum status: { active: 0, archived: 1 }
end

Note that when an array is used, the implicit mapping from the values to database integers is derived from the order the values appear in the array. In the example, :active is mapped to 0 as it's the first element, and :archived is mapped to 1. In general, the i-th element is mapped to i-1 in the database.

Therefore, once a value is added to the enum array, its position in the array must be maintained, and new values should only be added to the end of the array. To remove unused values, the explicit hash syntax should be used.

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

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

Use that class method when you need to know the ordinal value of an enum. For example, you can use that when manually building SQL strings:

Conversation.where("status <> ?", Conversation.statuses[:archived])

You can use the :_prefix or :_suffix options when you need to define multiple enums with same values. If the passed value is true, the methods are prefixed/suffixed with the name of the enum. It is also possible to supply a custom value:

class Conversation < ActiveRecord::Base
  enum status: [:active, :archived], _suffix: true
  enum comments_status: [:active, :inactive], _prefix: :comments
end

With the above example, the bang and predicate methods along with the associated scopes are now prefixed and/or suffixed accordingly:

conversation.active_status!
conversation.archived_status? # => false

conversation.comments_inactive!
conversation.comments_active? # => false
Methods
E
Instance Public methods
enum(definitions)
    # File activerecord/lib/active_record/enum.rb
150 def enum(definitions)
151   klass = self
152   enum_prefix = definitions.delete(:_prefix)
153   enum_suffix = definitions.delete(:_suffix)
154   enum_scopes = definitions.delete(:_scopes)
155   definitions.each do |name, values|
156     assert_valid_enum_definition_values(values)
157     # statuses = { }
158     enum_values = ActiveSupport::HashWithIndifferentAccess.new
159     name = name.to_s
160 
161     # def self.statuses() statuses end
162     detect_enum_conflict!(name, name.pluralize, true)
163     singleton_class.define_method(name.pluralize) { enum_values }
164     defined_enums[name] = enum_values
165 
166     detect_enum_conflict!(name, name)
167     detect_enum_conflict!(name, "#{name}=")
168 
169     attr = attribute_alias?(name) ? attribute_alias(name) : name
170     decorate_attribute_type(attr, :enum) do |subtype|
171       EnumType.new(attr, enum_values, subtype)
172     end
173 
174     _enum_methods_module.module_eval do
175       pairs = values.respond_to?(:each_pair) ? values.each_pair : values.each_with_index
176       pairs.each do |label, value|
177         if enum_prefix == true
178           prefix = "#{name}_"
179         elsif enum_prefix
180           prefix = "#{enum_prefix}_"
181         end
182         if enum_suffix == true
183           suffix = "_#{name}"
184         elsif enum_suffix
185           suffix = "_#{enum_suffix}"
186         end
187 
188         value_method_name = "#{prefix}#{label}#{suffix}"
189         enum_values[label] = value
190         label = label.to_s
191 
192         # def active?() status == "active" end
193         klass.send(:detect_enum_conflict!, name, "#{value_method_name}?")
194         define_method("#{value_method_name}?") { self[attr] == label }
195 
196         # def active!() update!(status: 0) end
197         klass.send(:detect_enum_conflict!, name, "#{value_method_name}!")
198         define_method("#{value_method_name}!") { update!(attr => value) }
199 
200         # scope :active, -> { where(status: 0) }
201         # scope :not_active, -> { where.not(status: 0) }
202         if enum_scopes != false
203           klass.send(:detect_negative_condition!, value_method_name)
204 
205           klass.send(:detect_enum_conflict!, name, value_method_name, true)
206           klass.scope value_method_name, -> { where(attr => value) }
207 
208           klass.send(:detect_enum_conflict!, name, "not_#{value_method_name}", true)
209           klass.scope "not_#{value_method_name}", -> { where.not(attr => value) }
210         end
211       end
212     end
213     enum_values.freeze
214   end
215 end