EnumAccessor - Simple enum fields for ActiveRecord
EnumAccessor lets you define enum for attributes, and store them as integer in the database.
It is very similar to Official Rails 4.1 Implementation, but EnumAccessor offers quite a few advantages, with even fewer lines of code. :) Compare rails version with enum_accessor.
- No-conflict safe predicate methods (
user.status_active?
instead ofuser.active?
) - Validation
- Scope
- Translation
- Forms
Compatible with ActiveRecord 3 or later.
If you want to handle multiple flags (bit array) with a single integer column, take a look at ActiveFlag.
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' # Takes String or Symbol
user.gender # => "female"
user.gender_female? # => true
user.gender_raw # => 0
user.gender = :male
user.gender_female? # => false
user.gender_raw # => 1
user.gender_male!
user.gender_male? # => true
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(:female)
Also takes multiple values.
User.where_status(:active, :pending)
To use under direct where
context (e.g. find_by
or find_or_create_by
), pass integer value.
Social.find_or_create_by(kind: Social.kinds[:facebook], external_id: facebook_user_id)
Validations
By default, models are validated using inclusion
. To disable, pass false
to validates
option.
enum_accessor :status, [:on, :off], validates: false
You can also pass validation options.
enum_accessor :status, [:on, :off], validates: { allow_nil: true }
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' }
Forms
Thanks to the translation support, forms just work as you expect. Pass an inverted human_*
hash for the select tag options.
= form_for @user do |f|
= f.select :gender, User.human_genders.invert
Changelog
- v2.3:
Added
gender_female!
bang setter - v2.2:
- Added
where_gender_not(:female)
scope
- Added
- v2.1:
- Support for uniqueness validation
- v2.0:
- Reworked to remove the "dict" methods. Now
User.genders.dict
isUser.genders
andUser.genders.human_dict
isUser.human_genders
- Reworked to remove the "dict" methods. Now
- v1.1:
- Validate by default again.
- Added
:class_attribute
option to specify class attribute to hold definitions - Cache translations on the fly
- v1.0:
- Drop support for Ruby 1.8
- Now getter method returns a String rather than a Symbol
- Do not validate by default
- Added
where_gender(:female)
scope - Removed the
_raw=
as setter automatically handles both types - Removed constants (e.g.
User::GENDERS
) and now use the class attribute to save the definition
- v0.3: Add support for
find_or_create_by
- just pass integer value
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 most of them define enum values as top-level predicate methods, which can cause method conflict. (user.active?
vs user.status_active?
)
Also, EnumAccessor has one of the simplest code base, so that you can easily hack on.