Enumerated attributes with I18n and ActiveRecord/Mongoid/MongoMapper support
Add this line to your application's Gemfile:
gem 'enumerize'
And then execute:
$ bundle
Or install it yourself as:
$ gem install enumerize
Basic:
class User
extend Enumerize
enumerize :sex, in: [:male, :female]
end
Note that enumerized values are just identificators so if you want to use multi-word, etc. values you should use I18n
feature.
ActiveRecord:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :sex
t.string :role
t.timestamps
end
end
end
class User < ActiveRecord::Base
extend Enumerize
enumerize :sex, in: [:male, :female], default: lambda { |user| SexIdentifier.sex_for_name(user.name).to_sym }
enumerize :role, in: [:user, :admin], default: :user
end
Mongoid:
class User
include Mongoid::Document
extend Enumerize
field :role
enumerize :role, in: [:user, :admin], default: :user
end
MongoMapper:
class User
include MongoMapper::Document
extend Enumerize
key :role
enumerize :role, in: [:user, :admin], default: :user
end
I18n:
en:
enumerize:
user:
sex:
male: "Male"
female: "Female"
or if you use sex
attribute across several models you can use defaults
scope:
en:
enumerize:
defaults:
sex:
male: "Male"
female: "Female"
You can also pass i18n_scope
option to specify scope (or array of scopes) storring the translations. Note that i18n_scope
option does not accept scope as array:
class Person
extend Enumerize
extend ActiveModel::Naming
enumerize :sex, in: %w[male female], i18n_scope: "sex"
enumerize :color, in: %w[black white], i18n_scope: ["various.colors", "colors"]
end
# localization file
en:
sex:
male: "Male"
female: "Female"
various:
colors:
black: "Black"
colors:
white: "White"
Note that if you want to use I18n feature with plain Ruby object don't forget to extend it with ActiveModel::Naming
:
class User
extend Enumerize
extend ActiveModel::Naming
end
get attribute value:
@user.sex_text # or @user.sex.text
get all values for enumerized attribute:
User.sex.values # or User.enumerized_attributes[:sex].values
use it with forms (it supports :only
and :except
options):
<%= form_for @user do |f| %>
<%= f.select :sex, User.sex.options %>
<% end %>
Boolean methods:
user.sex = :male
user.sex.male? #=> true
user.sex.female? #=> false
Predicate methods:
class User
extend Enumerize
enumerize :sex, in: %w(male female), predicates: true
end
user = User.new
user.male? # => false
user.female? # => false
user.sex = 'male'
user.male? # => true
user.female? # => false
Using prefix:
class User
extend Enumerize
enumerize :sex, in: %w(male female), predicates: { prefix: true }
end
user = User.new
user.sex = 'female'
user.sex_female? # => true
Use :only
and :except
options to specify what values create predicate methods for.
To make some attributes shared across different classes it's possible to define them in a separate module and then include it into classes:
module PersonEnumerations
extend Enumerize
enumerize :sex, in: %w[male female]
end
class Person
include PersonEnumerations
end
class User
include PersonEnumerations
end
It's also possible to store enumerized attribute value using custom values (e.g. integers). You can pass a hash as :in
option to achieve this:
class User < ActiveRecord::Base
extend Enumerize
enumerize :role, in: {:user => 1, :admin => 2}
end
user = User.new
user.role = :user
user.role #=> 'user'
user.role_value #=> 1
User.role.find_value(:user).value #=> 1
User.role.find_value(:admin).value #=> 2
ActiveRecord scopes:
class User < ActiveRecord::Base
extend Enumerize
enumerize :sex, :in => [:male, :female], scope: true
enumerize :status, :in => { active: 1, blocked: 2 }, scope: :having_status
end
User.with_sex(:female)
# SELECT "users".* FROM "users" WHERE "users"."sex" IN ('female')
User.without_sex(:male)
# SELECT "users".* FROM "users" WHERE "users"."sex" NOT IN ('male')
User.having_status(:blocked).with_sex(:male, :female)
# SELECT "users".* FROM "users" WHERE "users"."status" IN (2) AND "users"."sex" IN ('male', 'female')
Array-like attributes with plain ruby objects:
class User
extend Enumerize
enumerize :interests, in: [:music, :sports], multiple: true
end
user = User.new
user.interests << :music
user.interests << :sports
and with ActiveRecord:
class User < ActiveRecord::Base
extend Enumerize
serialize :interests, Array
enumerize :interests, in: [:music, :sports], multiple: true
end
If you are using SimpleForm gem you don't need to specify input type (:select
by default) and collection:
<%= simple_form_for @user do |f| %>
<%= f.input :sex %>
<% end %>
and if you want it as radio buttons:
<%= simple_form_for @user do |f| %>
<%= f.input :sex, :as => :radio_buttons %>
<% end %>
If you are using Formtastic gem you also don't need to specify input type (:select
by default) and collection:
<%= semantic_form_for @user do |f| %>
<%= f.input :sex %>
<% end %>
and if you want it as radio buttons:
<%= semantic_form_for @user do |f| %>
<%= f.input :sex, :as => :radio %>
<% end %>
Also you can use builtin RSpec matcher:
class User
extend Enumerize
enumerize :sex, in: [:male, :female], default: :male
end
describe User do
it { should enumerize(:sex).in(:male, :female) }
it { should enumerize(:sex).in(:male, :female).with_default(:male) }
# or with RSpec 3 expect syntax
it { is_expected.to enumerize(:sex).in(:male, :female) }
end
You can use the RSpec matcher with shoulda in your tests by adding two lines in your test_helper.rb
inside class ActiveSupport::TestCase
definition:
class ActiveSupport::TestCase
ActiveRecord::Migration.check_pending!
require 'enumerize/integrations/rspec'
extend Enumerize::Integrations::RSpec
...
end
Enumerize integrates with the following automatically:
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request