Enumerations ============ [![Gem Version](https://badge.fury.io/rb/enumerations.svg)](https://badge.fury.io/rb/enumerations) [![Maintainability](https://api.codeclimate.com/v1/badges/c3b96c5afceaa9be2173/maintainability)](https://codeclimate.com/github/infinum/enumerations/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/c3b96c5afceaa9be2173/test_coverage)](https://codeclimate.com/github/infinum/enumerations/test_coverage) [![Build Status](https://semaphoreci.com/api/v1/infinum/enumerations/branches/master/shields_badge.svg)](https://semaphoreci.com/infinum/enumerations) Rails plugin for Enumerations in ActiveRecord models. Installation ============ Inside your `Gemfile` add the following: ```ruby gem 'enumerations' ``` Usage ===== ## Defining enumerations Create a model for your enumerations: ```ruby 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: ```ruby 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: ```ruby class Post < ActiveRecord::Base enumeration :status validates :status, presence: true end ``` You can pass attributes to specify which enumeration and which column to use: ```ruby 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: ```ruby @post = Post.first @post.status = Status.draft @post.save ``` Or you can set enumerations by `symbol`: ```ruby @post.status = Status.find(:draft) ``` > If you try to set value that is not an Enumeration value (except `nil`), you will get an `Enumerations::InvalidValueError` exception. Also, you can set enumeration value like this: ```ruby @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: ```ruby class Post < ActiveRecord::Base enumeration :status end @post.status_draft! ``` ```ruby class User < ActiveRecord::Base enumeration :role end @user.role_admin! ``` ```ruby class User < ActiveRecord::Base enumeration :type, class_name: Role end @user.type_editor! ``` ## Finder methods Find enumerations by `id`: ```ruby @post.status = Status.find(2) # => Review pending @post.save ``` Other finding methods: ```ruby # 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: ```ruby @post.status == :published # => true @post.status == 'published' # => true @post.status == Status.published # => true @post.status.published? # => true ``` Get all enumerations: ```ruby Status.all ``` ## Filtering methods Enumerations can be filtered with `where` method, similar to `ActiveRecord::QueryMethods#where`. ```ruby 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: ```ruby 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: ```ruby with_#{enumeration_name}(enumeration, ...) without_#{enumeration_name}(enumeration, ...) ``` Examples: ```ruby 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 []> ``` ```ruby 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: ```ruby %p = f.label :status %br = f.collection_select :status, Status.all, :symbol, :name ``` Advanced Usage ===== Except `name` you can specify any other attributes to your enumerations: ```ruby 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`. ```ruby 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: ```ruby 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: ```yaml --- 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: ```ruby # 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 two configuration options. You can customize primary key and foreign key suffix. Just add initializer file to `config/initializers/enumerations.rb`. Example of configuration: ```ruby # config/initializers/enumerations.rb Enumerations.configure do |config| config.primary_key = :id config.foreign_key_suffix = :id config.translate_attributes = 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: ```ruby 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: ```ruby # 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 per enumeration class, you can use `primary_key=` class method: ```ruby class Status < Enumerations::Base self.primary_key = :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: ```sql 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: ```ruby 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](https://www.postgresql.org/docs/9.1/static/datatype-enum.html) you can find more informations about ENUM types. Contributing ============ 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request Credits ======= **Enumerations** is maintained and sponsored by [Infinum](https://infinum.co) Copyright © 2010 - 2018 Infinum Ltd. License ======= The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).