Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

EnumAccessor - Simple enum fields for ActiveRecord

branch: master

Merge pull request #1 from lest/patch-1

Add enumerize to the list of other solutions
latest commit 45cbdfd736
Kenn Ejima authored September 21, 2012
Octocat-spinner-32 lib Added class-level human methods. Bump to 0.2.0 September 17, 2012
Octocat-spinner-32 spec Added class-level human methods. Bump to 0.2.0 September 17, 2012
Octocat-spinner-32 .gitignore initial commit September 16, 2012
Octocat-spinner-32 .rspec initial commit September 16, 2012
Octocat-spinner-32 Gemfile initial commit September 16, 2012
Octocat-spinner-32 LICENSE.txt initial commit September 16, 2012
Octocat-spinner-32 README.md Add enumerize to the list of other solutions September 21, 2012
Octocat-spinner-32 Rakefile initial commit September 16, 2012
Octocat-spinner-32 enum_accessor.gemspec Fixing gemspec. September 16, 2012
README.md

EnumAccessor - Simple enum fields for ActiveRecord

EnumAccessor lets you define enum for attributes, and store them as integer in the database.

Compatible with ActiveRecord 3 or later.

Usage

Add this line to your application's Gemfile.

gem 'enum_accessor'

Define enum_accessor in a model class.

class User < ActiveRecord::Base
  enum_accessor :gender, [ :female, :male ]
end

And now you have a set of methods and constants.

user = User.new
user.gender             # => :female
user.gender_male?       # => false
user.gender_raw         # => 0

user.gender = :male
user.gender_male?       # => true
user.gender_raw         # => 1

User.genders            # => { :female => 0, :male => 1 }
User::GENDERS           # => { "female" => 0, "male" => 1 }

Notice that zero-based numbering is used as database values.

Your migration should look like this.

create_table :users do |t|
  t.integer :gender, :default => 0
end

Optionally, it would be a good idea to add :limit => 1 on the column for even better space efficiency when the enum set is small.

Manual coding

There are times when it makes more sense to manually pick particular integer values for the mapping.

In such cases, just pass a hash with coded integer values.

enum_accessor :status, ok: 200, not_found: 404, internal_server_error: 500

Scoping query

For querying purpose, use User.genders method to retrieve internal integer values.

User.where(gender: User.genders(:female))

Validations

You can pass custom validation options to validates_inclusion_of.

enum_accessor :status, [ :on, :off ], validation_options: { message: "incorrect status" }

Or skip validation entirely.

enum_accessor :status, [ :on, :off ], validate: false

Translation

EnumAccessor supports i18n just as ActiveModel does.

For instance, create a Japanese translation in config/locales/ja.yml

ja:
  enum_accessor:
    gender:
      female: 
      male: 

and now human_* methods return a translated string. It defaults to humanize method nicely as well.

I18n.locale = :ja
user.human_gender     # => '女'
User.human_genders    # => { :female => '女', :male => '男' }

I18n.locale = :en
user.human_gender     # => 'Female'
User.human_genders    # => { :female => 'Female', :male => 'Male' }

Why enum keys are internally stored as strings rather than symbols?

Because params[:gender].to_sym is dangerous. It could lead to problems like memory leak, slow symbol table lookup, or even DoS attack. If a user sends random strings for the parameter, it generates uncontrollable number of symbols, which can never be garbage collected, and eventually causes symbol table overflow (RuntimeError), eating up gigabytes of memory.

For the same reason, ActiveSupport::HashWithIndifferentAccess (which is used for params) keeps hash keys as string internally.

https://github.com/rails/rails/blob/master/activesupport/lib/active_support/hash_with_indifferent_access.rb

Other solutions

There are tons of similar gems out there. Then why did I bother creating another one myself rather than sending pull requests to one of them? Because each one of them has incompatible design policies than EnumAccessor.

  • simple_enum
    • Pretty close to EnumAccessor feature-wise but requires *_cd suffix for the database column, which makes AR scopes ugly.
    • Enum values are defined as top-level predicate methods, which could conflict with existing methods. Also you can't define multiple enums to the same model. In some use cases, predicate methods are not necessary and you just want to be on the safe side.
  • enumerated_attribute
    • Top-level predicate methods. Many additional methods are coupled with a specific usage assumption.
  • enum_field
    • Top-level predicate methods.
  • coded_options
  • active_enum
  • classy_enum
  • enumerize

Also, EnumAccessor has one of the simplest code base, so that you can easily hack on.

Something went wrong with that request. Please try again.