- 9.1 Scopes
- 9.2 Callbacks
- 9.3 Calculation Methods
- 9.4 Single-Table Inheritance (STI)
- 9.5 Abstract Base Model Classes
- 9.6 Polymorphic has_many Relationships
- 9.7 Enums
- 9.8 Foreign-Key Constraints
- 9.9 Modules for Reusing Common Behavior
- 9.10 Modifying Active Record Classes at Runtime
- 9.11 Using Value Objects
- 9.12 Nonpersisted Models
- 9.13 PostgreSQL Enhancements
- 9.14 Conclusion
9.7 Enums
One of the newest additions to Active Record introduced in Rails 4.1 is the ability to set an attribute as an enumerable. Once an attribute has been set as an enumerable, Active Record will restrict the assignment of the attribute to a collection of predefined values.
To declare an enumerable attribute, use the enum macro-style class method, passing it an attribute name and an array of status values that the attribute can be set to.
1 class Post < ActiveRecord::Base
2 enum status: %i(draft published archived)
3 ...
4 end
Active Record implicitly maps each predefined value of an enum attribute to an integer; therefore, the column type of the enum attribute must be an integer as well. By default, an enum attribute will be set to nil. To set an initial state, one can set a default value in a migration. It’s recommended to set this value to the first declared status, which would map to 0.
1 class CreatePosts < ActiveRecord::Migration
2 def change
3 create_table :posts do |t|
4 t.integer :status, default: 0
5 end
6 end
7 end
For instance, given our example, the default status of a Post model would be “draft”:
>> Post.new.status
=> "draft"
You should never have to work with the underlying integer data type of an enum attribute, as Active Record creates both predicate and bang methods for each status value.
1 post.draft!
2 post.draft? # => true
3 post.published? # => false
4 post.status # => "draft"
5
6 post.published!
7 post.published? # => true
8 post.draft? # => false
9 post.status # => "published"
10
11 post.status = nil
12 post.status.nil? # => true
13 post.status # => nil
Active Record also provides scope methods for each status value. Invoking one of these scopes will return all records with that given status.
Post.draft
# Post Load (0.1ms) SELECT "posts".* FROM "posts"
WHERE "posts"."status" = 0
With the addition of the enum attribute, Active Record finally has a simple state machine out of the box. This feature alone should simplify models that had previously depended on multiple boolean fields to manage state. If you require more advanced functionality, such as status transition callbacks and conditional transitions, it’s still recommended to use a full-blown state machine like state_machine.10