Enumerations
Rails plugin for Enumerations in ActiveRecord models.
Installation
Inside your Gemfile
add the following:
gem 'enumerations'
Usage
Defining enumerations
Create a model for your enumerations:
class Status < Enumerations::Base
values draft: { name: 'Draft' },
review_pending: { name: 'Review pending' },
published: { name: 'Published' }
end
Or you can use value
method for defining your enumerations:
class Status < Enumerations::Base
value :draft, name: 'Draft'
value :review_pending, name: 'Review pending'
value :published, name: 'Published'
end
Include enumerations for integer fields in other models:
class Post < ActiveRecord::Base
enumeration :status
validates :status, presence: true
end
You can pass attributes to specify which enumeration and which column to use:
class Post < ActiveRecord::Base
enumeration :status,
foreign_key: :post_status, # specifies which column to use
class_name: Post::Status # specifies the class of the enumerator
validates :post_status, presence: true
end
Attribute foreign_key
you can pass as a String
or a Symbol
. Attribute class_name
can be set as a String
, a Symbol
or a String
.
Setting enumeration value to objects
Set enumerations:
@post = Post.first
@post.status = Status.draft
@post.save
Or you can set enumerations by symbol
:
@post.status = Status.find(:draft)
If you try to set value that is not an Enumeration value (except
nil
), you will get anEnumerations::InvalidValueError
exception. You can turn this exception off in configuration.
Also, you can set enumeration value like this:
@post.status_draft!
When you include enumerations into your model, you'll get methods for setting each enumeration value. Each method name is consists from enumeration name and enumeration value name with ! at the end. Examples:
class Post < ActiveRecord::Base
enumeration :status
end
@post.status_draft!
class User < ActiveRecord::Base
enumeration :role
end
@user.role_admin!
class User < ActiveRecord::Base
enumeration :type, class_name: Role
end
@user.type_editor!
Finder methods
Find enumerations by id
:
@post.status = Status.find(2) # => Review pending
@post.save
Other finding methods:
# Find by key as a Symbol
Status.find(:review_pending) # => Review pending
# Find by key as a String
Status.find('draft') # => Draft
# Find by multiple attributes
Status.find_by(name: 'None', visible: true) # => None
Compare enumerations:
@post.status == :published # => true
@post.status == 'published' # => true
@post.status == Status.published # => true
@post.status.published? # => true
Get all enumerations:
Status.all
Filtering methods
Enumerations can be filtered with where
method, similar to ActiveRecord::QueryMethods#where
.
Role.where(admin: true) # => [Role.admin, Role.editor]
Role.where(admin: true, active: true) # => [Role.admin]
Scopes on model
With enumerations, you'll get scope for each enumeration value in the following format:
with_#{enumeration_name}_#{enumeration_value_name}
without_#{enumeration_name}_#{enumeration_value_name}
or you can use the following scope and pass an array of enumerations:
with_#{enumeration_name}(enumeration, ...)
without_#{enumeration_name}(enumeration, ...)
Examples:
class Post < ActiveRecord::Base
enumeration :status
end
Post.with_status_draft # => <#ActiveRecord::Relation []>
Post.without_status_review_pending # => <#ActiveRecord::Relation []>
Post.with_status(:draft) # => <#ActiveRecord::Relation []>
Post.without_status(:draft) # => <#ActiveRecord::Relation []>
Post.with_status(Status.draft) # => <#ActiveRecord::Relation []>
Post.with_status(:draft, :review_pending) # => <#ActiveRecord::Relation []>
Post.with_status(Status.draft, 'published') # => <#ActiveRecord::Relation []>
Post.with_status([:draft, :review_pending]) # => <#ActiveRecord::Relation []>
class Post < ActiveRecord::Base
enumeration :my_status, class_name: Status
end
Post.with_my_status_draft # => <#ActiveRecord::Relation []>
Post.with_my_status_review_pending # => <#ActiveRecord::Relation []>
Post.with_my_status(:draft) # => <#ActiveRecord::Relation []>
Post.without_my_status(:draft) # => <#ActiveRecord::Relation []>
Each scope returns all records with specified enumeration value.
Forms usage
Use in forms:
%p
= f.label :status
%br
= f.collection_select :status, Status.all, :symbol, :name
Validating input
Enumerations will by default raise an exception if you try to set an invalid value. This prevents usage of validations, which you might want to add if you're developing an API and have to return meaningful errors to API clients.
You can enable validations by first disabling error raising on invalid input (see configuration). Then, you should add an inclusion validation to enumerated attributes:
class Post < ActiveRecord::Base
enumeration :status
validates :status, inclusion: { in: Status.all }
end
You'll now get an appropriate error message when you insert an invalid value:
> post = Post.new(status: 'invalid')
> post.valid?
=> false
> post.errors.full_messages.to_sentence
=> "Status is not included in the list"
> post.status
=> "invalid"
Advanced Usage
Except name
you can specify any other attributes to your enumerations:
class Status < Enumerations::Base
value :draft, id: 1, name: 'Draft', published: false
value :review_pending, id: 2, name: 'Review pending', description: 'Some description...'
value :published, id: 3, name: 'Published', published: true
value :other # passing no attributes is also allowed
end
Every enumeration has id
, name
, description
and published
methods.
If you call method that is not in attribute list for enumeration, it will return nil
.
Status.review_pending.description # => 'Some description...'
Status.draft.description # => nil
For each attribute, you have predicate method. Predicate methods are actually calling present?
method on attribute value:
Status.draft.name? # => true
Status.draft.published? # => false
Status.published.published? # => true
Status.other.name? # => false
Translations
Enumerations uses power of I18n API (if translate_attributes configuration is set to true) to enable you to create a locale file for enumerations like this:
---
en:
enumerations:
status:
draft:
name: Draft
description: Article draft...
...
role:
admin:
name: Administrator
You can separate enumerations locales into a separate
*.yml
files. Then you should add locale file paths to I18n load path:
# config/initializers/locale.rb
# Where the I18n library should search for translation files (e.g.):
I18n.load_path += Dir[Rails.root.join('config', 'locales', 'enumerations', '*.yml')]
Configuration
Basically no configuration is needed.
Enumerations has four configuration options.
You can customize primary key, foreign key suffix, whether to translate attributes and whether to raise Enumerations::InvalidValueError
exception when setting invalid values.
Just add initializer file to config/initializers/enumerations.rb
.
Example of configuration:
# config/initializers/enumerations.rb
Enumerations.configure do |config|
config.primary_key = :id
config.foreign_key_suffix = :id
config.translate_attributes = true
config.raise_invalid_value_error = true
end
By default, primary_key
and foreign_key_suffix
are set to nil
.
By default model enumeration value is saved to column with same name as enumeration. If you set enumeration as:
enumeration :status
then model should have status
column (as String
type).
If you want save an ID
to this column, you can set foreign_key_suffix
to id
.
Then model should have status_id
column.
If you set primary_key
then you need provide this attribute for all enumerations values.
Also, value from primary_key
attribute will be stored to model as enumeration value.
For example:
# with default configuration
post = Post.new
post.status = Status.draft # => post.status = 'draft'
# with configured primary_key and foreign_key_suffix:
Enumerations.configure do |config|
config.primary_key = :id
config.foreign_key_suffix = :id
end
class Status < Enumerations::Base
value :draft, id: 1, name: 'Draft'
value :review_pending, id: 2, name: 'Review pending',
value :published, id: 3, name: 'Published', published: true
end
post = Post.new
post.status = Status.draft # => post.status_id = 1
If you want to configure primary key or foreign key suffix per enumeration class, you can use primary_key=
and foreign_key_suffix=
class method:
class Status < Enumerations::Base
self.primary_key = :id
self.foreign_key_suffix = :id
value :draft, id: 1, name: 'Draft'
value :review_pending, id: 2, name: 'Review pending'
end
Database Enumerations
By default, enumeration values are saved to database as String
.
If you want, you can define Enum
type in database:
CREATE TYPE status AS ENUM ('draft', 'review_pending', 'published');
Then you'll have enumeration as type in database and you can use it in database migrations:
add_column :posts, :status, :status, index: true
With configuration option primary_key
, you can store any type you want (e.g. Integer
).
Also, for performance reasons, you should add indices to enumeration column.
Here you can find more informations about ENUM types.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
Credits
Enumerations is maintained and sponsored by Infinum
Copyright © 2010 - 2018 Infinum Ltd.
License
The gem is available as open source under the terms of the MIT License.