Dec 23

Date published: 12-23-2013

Rails version: 4.0.0

Enumeration

One tool that programmers use fairly often to store information is the enumeration. It’s most often used to store the state of objects in the model so the program knows how to interpret other data in that model. As common as enumerations are, it’s surprising that Rails doesn’t have a standard for handling it. In this tutorial, we’ll learn how to create an enumeration that follows Rails conventions and symbols.

Copy the template site

The first thing we need to do is copy our template site and create a new project from it. Open a terminal window and switch to the directory where the template Bootstrap site is located. Run the following commands:

$ cp -r template_bootstrap_site/ simple_enumeration
$ cd simple_enumeration/

It’s a good idea now to change the module name just like we did in this tutorial in the section titled, “Update the module information.” Make sure to commit your changes to Git before moving on.

User status

One common use of enumeration that sites might use is for keeping track of the status of user accounts. Let’s say our site wants to keep track of users who are active, inactive, or suspended. We need to create a user model with a username and status, so type in the terminal window:

$ rails generate model User username:string status:string
      invoke  active_record
      create    db/migrate/20131216194048_create_users.rb
      create    app/models/user.rb
$ rake db:migrate
==  CreateUsers: migrating ====================================================
-- create_table(:users)
   -> 0.0020s
==  CreateUsers: migrated (0.0021s) ===========================================
$ annotate
Annotated (1): User

At the moment the User model is empty, but I’d like to get everything else I’ll need setup to be able to see information on the site. Let’s setup the controller and views before we get into how to handle enumeration in the model. Change the config/routes.rb file to this:

  root 'users#index'
  get '/about', to: 'static_pages#about'

  resources :users, only: [:new, :create, :index]

Create a new file at app/controllers/users_controller.rb and fill it in with this:

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)

    if @user.save
      redirect_to root_path
    else
      render 'new'
    end
  end

  def index
    @users = User.all
  end

  private
  def user_params
    params.require(:user).permit(:username)
  end
end

We need to modify the header to have a convenient link to create new users. Add this link to app/views/layouts/_header.html.erb:

            <li><%= link_to "New user", new_user_path %></li>

Create a new file called app/views/users/new.html.erb and fill it in:

<% provide(:title, "New User") %>
<h1>New User</h1>
<%= form_for :user, url: users_path do |f| %>
  <% if @user.errors.any? %>
    <div id="error_explanation">
      <div class="alert alert-error">
        The form contains <%= pluralize(@user.errors.count, "error") %>.
      </div>
      <ul>
      <% @user.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <p>
    <%= f.label :username %><br>
    <%= f.text_field :username %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

Create another file called app/views/users/index.html.erb and fill it in:

<% provide(:title, "All Users") %>
<h1>All Users</h1>
<div class="row">
  <div class="span3">Username</div>
  <div class="span9">Status</div>
</div>
<% @users.each do |user| %>
  <div class="row">
    <div class="span3"><%= user.username %></div>
    <div class="span9">
      <% if user.status == :active %>
        <p>This user is active.</p>
      <% elsif user.status == :inactive %>
        <p>This user is no longer active.</p>
      <% elsif user.status == :suspended %>
        <p>The user was suspended.</p>
      <% elsif user.status.nil? %>
        <p>No status for this user.</p>
      <% else %>
        <p>Don't understand status: <%= user.status.to_s %></p>
      <% end %>
    </div>
  </div>
<% end %>
<% if @users.empty? %>
  <div class="row">
    There are no users to show.
  </div>
<% end %>

Start the Rails server and check out what we’ve done. If we create new users, they all show the message, “No status for this user.” This is what we expect because we don’t want allow the user to add their own status when they create their account. Let’s update our model so that it recognizes the types we want to use. Open app/models/user.rb and make the following changes:

class User < ActiveRecord::Base
  validates_inclusion_of :status, :in => [:active, :inactive, :suspended]

  def status
    value = read_attribute(:status)
    value.blank? ? nil : value.to_sym
  end

  def status= (value)
    write_attribute(:status, value.to_s)
  end
end

If you try to make a new user now, there will be an error message about the status. We need to make sure that a default status is set when new users are created, so let’s change our controller to do that. Open app/controllers/users_controller.rb and set the status after creating a new user:

  def create
    @user = User.new(user_params)
    @user.status = :active

Now any users we create will be active when the user is created, and our model should pass validation when we use the new user page. Try making a new user to make sure it’s working. Now what do we do to change the status of users? We can use the console to manually change the status of a user:

$ rails c
Loading development environment (Rails 4.0.0)
2.0.0-p195 :001 > user = User.first
  User Load (0.2ms)  SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
 => #<User id: 1, username: "Adam", status: "suspended", created_at: "2013-12-16 20:15:30", updated_at: "2013-12-16 20:35:09">
2.0.0-p195 :002 > user.status = :active
 => :active
2.0.0-p195 :003 > user.save
   (0.2ms)  begin transaction
  SQL (4.3ms)  UPDATE "users" SET "status" = ?, "updated_at" = ? WHERE "users"."id" = 1  [["status", "active"], ["updated_at", Mon, 16 Dec 2013 20:38:50 UTC +00:00]]
   (2.1ms)  commit transaction
 => true
2.0.0-p195 :004 > exit

In this example, the user named Adam had an account that was suspended. We set his status to active and saved it. Now if we reload the page in our browser we see his status has changed to active. In a real application, we would want to setup accounts as admins and give them pages to use to change the status of accounts, but that’s outside of the scope of this tutorial. Let’s save our changes in Git before we finish:

$ git add .
$ git commit -m "Created User with a status enumeration"

That’s all for this tutorial. Source code for the project can be found on Github at:

http://github.com/wordplay/simple_enumeration

Leave a Reply

preload preload preload