43

I have some constants that represent the valid options in one of my model's fields. What's the best way to handle these constants in Ruby?

John Topley
  • 113,588
  • 46
  • 195
  • 237
Miles
  • 1,561
  • 1
  • 15
  • 22
  • [This other question about Enumerations in Ruby seems to be very very similar](http://stackoverflow.com/questions/164714/how-can-i-use-c-style-enumerations-in-ruby) – Orion Edwards Nov 05 '08 at 20:46

6 Answers6

40

You can use an array or hash for this purpose (in your environment.rb):

OPTIONS = ['one', 'two', 'three']
OPTIONS = {:one => 1, :two => 2, :three => 3}

or alternatively an enumeration class, which allows you to enumerate over your constants as well as the keys used to associate them:

class Enumeration
  def Enumeration.add_item(key,value)
    @hash ||= {}
    @hash[key]=value
  end

  def Enumeration.const_missing(key)
    @hash[key]
  end   

  def Enumeration.each
    @hash.each {|key,value| yield(key,value)}
  end

  def Enumeration.values
    @hash.values || []
  end

  def Enumeration.keys
    @hash.keys || []
  end

  def Enumeration.[](key)
    @hash[key]
  end
end

which you can then derive from:

class Values < Enumeration
  self.add_item(:RED, '#f00')
  self.add_item(:GREEN, '#0f0')
  self.add_item(:BLUE, '#00f')
end

and use like this:

Values::RED    => '#f00'
Values::GREEN  => '#0f0'
Values::BLUE   => '#00f'

Values.keys    => [:RED, :GREEN, :BLUE]
Values.values  => ['#f00', '#0f0', '#00f']
marcosdsanchez
  • 2,529
  • 2
  • 17
  • 20
Codebeef
  • 43,508
  • 23
  • 86
  • 119
  • 2
    I decided to go with this solution. I have to say, it does just what I want and plays nicely with ActiveRecord. Thank you! :D – Miles Nov 06 '08 at 21:45
11

I put them directly in the model class, like so:

class MyClass < ActiveRecord::Base
  ACTIVE_STATUS = "active"
  INACTIVE_STATUS = "inactive"
  PENDING_STATUS = "pending"
end

Then, when using the model from another class, I reference the constants

@model.status = MyClass::ACTIVE_STATUS
@model.save
dusan
  • 9,104
  • 3
  • 35
  • 55
Micah
  • 17,584
  • 8
  • 40
  • 46
9

If it is driving model behavior, then the constants should be part of the model:

class Model < ActiveRecord::Base
  ONE = 1
  TWO = 2

  validates_inclusion_of :value, :in => [ONE, TWO]
end

This will allow you to use the built-in Rails functionality:

>> m=Model.new
=> #<Model id: nil, value: nil, created_at: nil, updated_at: nil>
>> m.valid?
=> false
>> m.value = 1
=> 1
>> m.valid?
=> true

Alternatively, if your database supports enumerations, then you can use something like the Enum Column plugin.

Dave
  • 91
  • 1
8

Rails 4.1 added support for ActiveRecord enums.

Declare an enum attribute where the values map to integers in the database, but can be queried by name.

class Conversation < ActiveRecord::Base
  enum status: [ :active, :archived ]
end

conversation.archived!
conversation.active? # => false
conversation.status  # => "archived"

Conversation.archived # => Relation for all archived Conversations

See its documentation for a detailed write up.

Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
  • I think this is the best answer. – Islam Azab Sep 16 '15 at 19:42
  • 1
    Be ready to get exceptions when you try to assign some value that is not in the list to the status field, and be ready to get name conflicts if you add another enum field with [:active] value. Rails implementation of this feature really awkward. – BitOfUniverse Jun 10 '16 at 15:03
  • i have made a gem for validating enums inclusion https://github.com/CristiRazvi/enum_attributes_validation – cristi_razvi Nov 13 '17 at 13:13
5

You can also use it within your model inside a hash like this:


class MyModel

  SOME_ATTR_OPTIONS = {
    :first_option => 1,
    :second_option => 2, 
    :third_option => 3
  }
end

And use it like this:



if x == MyModel::SOME_ATTR_OPTIONS[:first_option]
  do this
end

Dema
  • 6,867
  • 11
  • 40
  • 48
0

You can also group constants into subjects, using a module --

class Runner < ApplicationRecord
    module RUN_TYPES
        SYNC = 0
        ASYNC = 1
    end
end

And then have,

> Runner::RUN_TYPES::SYNC
 => 0
> Runner::RUN_TYPES::ASYNC
 => 1
Ady Rosen
  • 33
  • 5