Rails 4.1 has recently been released and it came out with the usual bag of goodies, one of the most notable being ActiveRecord enums, a handy new feature that simplifies the creation and use of state attributes for your models.
Consider the following use case: our app needs users, and each of them has a state that can be
How would have we addressed the task in the past? Well, probably by adding a string or integer field to the
users table in order to hold the state value, and by writing a few scope methods to query the table. Nowadays it’s much simpler: you just need to write a migration that adds the field to the table:
class AddStatus < ActiveRecord::Migration def change add_column :users, :state, :integer end end
and add the enum macro to the User class:
class User enum state: [:registered, :active, :blocked] end
Let’s see in details what we’re getting. The user state is
nil by default; we can query each state label to see if it matches the current user’s state:
user = User.new user.state # => nil user.registered? # => false user.state = :registered user.registered? # => true
Want to update the state and save the record in one command? Easy enough:
user.registered! user.persisted? # => true user.registered? # => true
We also get handy scope methods for each state:
User.active # => #<ActiveRecord::Relation > User.registered # => #<ActiveRecord::Relation [#<User id: 7, status: 0...]>
We can even create users with a specific state using the enum scopes:
User.registered.create # => #<User id: 6, status: 1, ...>
Can we inspect the actual state column value, before typecast? Of course we can, but
#state_before_type_cast returns the enum label. So we’re going to use the
user.state # => "registered" user.state_before_type_cast # => "registered" user[:state] # => 0
So, now we can fully understand how it works: ActiveRecord actually stores in the database the integer corresponding to the enum label position inside the array that we provided to the <code>enum</code> macro.
Do you want a default value? Then add a default value to the state field:
class ChangeStatus < ActiveRecord::Migration def change change_column :users, :status, :integer, default: 1 end end
From now on all instantiated users will have a default state value corresponding to the position we provided in the migration:
user = User.new user.state # => "active"
Be aware there are a few reserved words that cannot be used as enum labels, most notably existing column names, existing class methods and a few more. If you happen to use one of them by mistake then the app will complain raising an error:
class User enum state: [:logger] end # => ArgumentError: You tried to define an enum named "state" on the model "User", but this will generate a class method "logger", which is already defined by Active Record.