Rails 4.1 ActiveRecord enums

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 registered, active or blocked.

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 [] method:


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.

If you are interested in the actual Rails implementation you can peep at the code and  tests on the github repository, of course. Have fun!

Notes

  1. gustaflindqvist reblogged this from mikamayhem
  2. mikamayhem posted this