With the release of Ruby on Rails 4.1 we've seen a number of cool addition to the framework. One of these additions is known as Active Record Enums. Active Record Enums provide a lightweight alternative to using status flags or look-up tables. In this article we will show you how to implement Active Record Enums in your Ruby on Rails Application.

To get started, we will first need to create a model. In our example we will create a simple model called Order which will represent an order you might find on an eCommerce site or in a store. To keep things simple this order model only has 5 fields: subtotal, tax, shipping, total, and status. Order status can be one of Cancelled, In Progress, Completed, or Invoiced. Lets create this model now. Open up a terminal and create this model in a new rails application.

Terminal Commands:

rails g model Order subtotal:decimal{11,2} tax:decimal{11,2} shipping:decimal{11,2} status:integer

Before we run a db:migrate, it's a good idea to add in a default value for status. Open up your migration file and modify it so that it looks like the code listed below.

db/migrate/XXXXXXXXXXXXXX_create_orders.rb:

class CreateOrders < ActiveRecord::Migration
  def change
    create_table :orders do |t|
      t.decimal :subtotal, precision: 11, scale: 2
      t.decimal :tax, precision: 11, scale: 2
      t.decimal :shipping, precision: 11, scale: 2
      t.decimal :total, precision: 11, scale: 2
      t.integer :status, default: 1

      t.timestamps
    end
  end
end

Now let's run a rake db:migrate to update the database with our new migration.

Terminal Commands:

rake db:migrate

Great, now that we have the model, let's add a bit of seed data so that we can play around without having to create new orders. Open up your seeds.rb file and add in the code listed below.

db/seeds.rb:

Order.delete_all
Order.create!( id: 1, subtotal: 12.00, tax: 0.00, shipping: 10.00, total: 22.00, status: 3)
Order.create!( id: 2, subtotal: 102.00, tax: 0.00, shipping: 22.00, total: 124.00, status: 2)
Order.create!( id: 3, subtotal: 100.00, tax: 7.00, shipping: 10.00, total: 117.00, status: 2)

Now let's run a rake db:seed in order to add the seed data to our database.

Terminal Commands:

rake db:seed

Excellent! Now open up your Order model and modify it so that it looks like the code listed below.

app/models/order.rb:

class Order < ActiveRecord::Base
  validates :subtotal, presence: true
  validates :tax, presence: true
  validates :shipping, presence: true
  validates :total, presence: true
  
  enum status: [ :cancelled, :in_progress, :completed, :invoiced ]
end

Of note in the above code is the enum status: line. This tells rails that we wish to use the status field as an enum. Now lets open up the rails console to play around a bit. Remember that you can exit the rails console at any time by typing exit.

Terminal Commands:

rails c

Let's start with the basics. First, you can get an order's status by using order.status:


o = order.first
o.status #=> "invoiced"

You can also humanize the result to pretty it up:


o.status.humanize #=> "Invoiced"

You can easily check to see if the order matches a certain status:


o.invoiced? #=> true

Finally, you can quickly set a new status:


o.completed! #=> Sets the order's status to "completed"
o.status #=> "completed"

We can also get a list of all statuses:


Order.statuses #=> {"cancelled"=>0, "in_progress"=>1, "completed"=>2, "invoiced"=>3}

You'll notice that each status has a corresponding number. These can be customize as I'll demonstrate later.

How do we utilize status in our views? Luckily it's pretty easy. Exit the Rails console and run the command below to create an orders controller.

Terminal Commands:

rails g controller Orders index edit update

Now open up your routes file and modify it so that it looks like the code listed below.

config/routes.rb:

Rails.application.routes.draw do
  resources :orders
  root to: "orders#index"
end

Now open up your orders controller and modify it so that it looks like the code listed below.

app/controllers/orders_controller.rb:

class OrdersController < ApplicationController
  def index
    @orders = Order.all
  end

  def edit
    @order = Order.find(params[:id])
    @statuses = Order.statuses
  end

  def update
    @order = Order.find(params[:id])
    @statuses = Order.statuses
    if @order.update_attributes(order_params)
      redirect_to orders_path, notice: "The order has been created."
    else
      render "edit"
    end
  end

private
  def order_params
    params.require(:order).permit(:subtotal, :tax, :shipping, :total, :status)
  end
end

Great! This is pretty much standard stuff, with the exception of the @statuses = Order.statuses line. This line of code collects the available statuses for use in a select box. Now let's quickly add bootstrap to our application to beautify things a bit. Open up your application layout and modify it so that it looks like the code listed below.

app/views/layouts/application.html.erb:

<!DOCTYPE html>
<html>
<head>
  <title>ActiveRecordEnumsExample</title>
  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
  <%= stylesheet_link_tag "http://yandex.st/bootstrap/3.1.1/css/bootstrap.min.css" %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

Great! Now let's create our index view for orders. Open up the index view for orders and modify it so that it looks like the code listed below.

app/views/orders/index.html.erb:

<div class="container">
  <h3>Orders</h3>
  <table class="table table-bordered table-striped">
    <tr>
      <th>Order No</th>
      <th>Subtotal</th>
      <th>Tax</th>
      <th>Shipping</th>
      <th>Total</th>
      <th>Order Status</th>
      <td>&nbsp;</td>
    </tr>
    <% @orders.each do |order| %>
      <tr>
        <td><%= order.id %></td>
        <td><%= number_to_currency order.subtotal %></td>
        <td><%= number_to_currency order.tax %></td>
        <td><%= number_to_currency order.shipping %></td>
        <td><%= number_to_currency order.total %></td>
        <td><%= order.status.humanize %></td>
        <td><%= link_to "Edit", edit_order_path(order), class: "btn btn-primary" %></td>
      </tr>
    <% end %>
  </table>
</div>

You'll notice the line calling order.status.humanize. This line displays the order status in human format (English in this example).

Now let's add code to our edit view for orders. Open up the edit view for orders and modify it so that it looks like the code listed below.

app/views/orders/edit.html.erb:

<div class="container">
  <h3>Editing Order No <%= @order.id %></h3>
  <hr />
  <% if @order.errors.any? %>
    <div class="alert alert-danger">
       <h2>
          <%= pluralize(@user.errors.count, "error")%>
              prohibited this user from being saved:  
          </h2>
          <ul>
            <%= @order.errors.full_messages.each do |message| %>
                <li><%= message %></li>
            <% end %>
          </ul>   
    </div>
  <% end %>

  <%= form_for @order do |f| %>
    <div class="row">
      <div class="col-xs-12 form-group">
        <%= f.label :subtotal, class: "control-label" %>
        <%= f.number_field :subtotal, class: "form-control" %>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-12 form-group">
        <%= f.label :tax %>
        <%= f.number_field :tax, class: "form-control" %>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-12 form-group">
        <%= f.label :shipping %>
        <%= f.number_field :shipping, class: "form-control" %>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-12 form-group">
        <%= f.label :total %>
        <%= f.number_field :total, class: "form-control" %>
      </div>
    </div>

    <div class="row">
      <div class="col-xs-12 form-group">
        <%= f.label :status %>
        <%= f.select :status, options_for_select(@statuses.collect { |s| [s[0].humanize, s[0]] }, selected: @order.status), {} , class: "form-control" %>
      </div>
    </div>

    <div class="row">
      <div class="col-xs-12 form-group">
        <%= f.submit "Save", class: "btn btn-primary" %>
      </div>
    </div>
  <% end %>
</div>

Of particular interest here is the line for creating the select box. We take the statuses hash, humanize it, and put it in the select box. The default value is merely set to the order status. if you were to view the status, you'd notice that the corresponding values are the actual status strings rather than integers.

Now if you start a rails server and navigate to http://localhost:3000 you'll see that you can quickly edit orders. Change statuses, etc.

A couple of additional notes:

  • If you want to specify the integer that each status belongs to, you can easily do so by modifying the enum line of your model so that it looks something like this: enum status: { cancelled: 0, in_progress: 1, completed: 2, invoiced: 3 }.
  • Avoid using the same enum value names inside the same class, even in different enums.

That's it! Thanks for reading!