RailsAttrEnum

Enums for Rails models

I created RailsAttrEnum as a way to create an enum-like structure similar to enums in C languages. You can specify the accepted identifiers for the possible integer values for the model's attribute as well have built-in validation to ensure only the values are accepted.

Usage

Here's an example given a class User with an attribute role:

# Example model User for a blog app
class User < ActiveRecord::Base
  extend RailsAttrEnum

  attr_enum :role, :admin, :editor, :author, :user
end

# Creates module `User::Role` with constants for each possible value
User::Role::ADMIN  == 0
User::Role::EDITOR == 1
User::Role::AUTHOR == 2
User::Role::USER   == 3

View other ways to define and customize enums

Helpers for Model Instances

A couple helpers methods are added to the model and the enum attribute.

Get the "display" label for the current value with the display_* method:

user = User.new(User::Role::ADMIN)
user.display_role == 'Admin'

You can check for a specific value with a query *? method:

user = User.new(User::Role::AUTHOR)
user.role.admin?  # false
user.role.editor? # false
user.role.author? # true
user.role.user?   # false

The query method works via a forwarding class, so the normal role and role= methods should work as expected.

NOTE: one caveat to this is if you try to use a hash map of the enum values to some other value. See below:

alt_label_map = {
  User::Role::ADMIN  => 'The admin user',
  User::Role::EDITOR => 'An editor',
  User::Role::AUTHOR => 'An author',
  User::Role::USER   => 'A user'
}

user = User.new(User::Role::EDITOR)
alt_label = alt_label_map[user.role]
alt_label == nil # not 'An editor'

If you want the hash to work as expected than call the .value method on the attribute:

alt_label = alt_label_map[user.role.value]
alt_label == 'An editor'

Thus, the .value method on the attribute gives the actual Fixnum value. There is also a .key method which gives the symbol key:

user = User.new(User::Role::ADMIN)
user.role.key == :admin

The attribute value can also be set with a bang *! method

user = User.new
user.role.user!
user.display_role == 'User'

user.role.author!
user.display_role == 'Author'

Scopes for Models

Convenient scopes are created for each possible enum value on the model class:

User.scope_admin  == User.where(role: User::Role::ADMIN)
User.scope_editor == User.where(role: User::Role::EDITOR)
User.scope_author == User.where(role: User::Role::AUTHOR)
User.scope_user   == User.where(role: User::Role::USER)

Enum Helper Methods

Helper methods are added to the actual Enum module as well.

get_label and get_key get the (surprise!) label and key for a given enum value:

User::Role.get_label(User::Role::ADMIN) == 'Admin'
User::Role.get_key(User::Role::USER) == :user

attr_name returns the attribute symbol

User::Role.attr_name == :role

keys returns all the enum keys

User::Role.keys == [:admin, :editor, :author, :user]

values returns all the enum values

User::Role.values == [0, 1, 2, 3]

labels returns all the enum labels

User::Role.labels == ['Admin', 'Editor', 'Author', 'User']

label_value_pairs returns an array of pairs of the label and value for each enum value. This is mainly a convenience method for something like the collection option for a select input in the Formtastic or ActiveAdmin (which uses Formtastic) gems:

User::Role.label_value_pairs ==
  [['Admin', 0], ['Editor', 1], ['Author', 2], ['User', 3]]

to_h and to_json return a hash and a json string representation of the enum, respectively. They both offer an only and an except option to specify if you only want the value or maybe only the label and key or if you want everything but key. NOTE: passing only key to only or excluding all but one key via except will give that single value (whether it's value, key, or label) instead of a hash. See below to understand:

# Default call with no options
User::Role.to_h == {
  'ADMIN'  => { key: :admin,  label: 'Admin',  value: 0 },
  'EDITOR' => { key: :editor, label: 'Editor', value: 1 },
  'AUTHOR' => { key: :author, label: 'Author', value: 2 },
  'USER'   => { key: :user,   label: 'User',   value: 3 }
}

# Call with a single symbol (would also work with `only: [:value]`)
# Notice the mapped values are not hashes like above because we only
# specified that we wanted the value
User::Role.to_h(only: :value) == {
  'ADMIN'  => 0,
  'EDITOR' => 1,
  'AUTHOR' => 2,
  'USER'   => 3
}

# Would also work with `except: [:value]`
User::Role.to_json(except: :value) ==
  "{\"ADMIN\":{\"key\":\"admin\",\"label\":\"Admin\"},\"EDITOR\":{\"key\":\"editor\",\"label\":\"Editor\"},\"AUTHOR\":{\"key\":\"author\",\"label\":\"Author\"},\"USER\":{\"key\":\"user\",\"label\":\"User\"}}"

Feedback and Pull Requests Welcome

This is my first real Rails gem, so I welcome all feedback and ideas. I hope this gem is as helpful to you as it has been to me in my own projects.