How to Use C# Style Enumerations in Ruby

How can I use C# style enumerations in Ruby?

Specifically, I would like to be able to perform logical tests against the set of values given some variable. Example would be the state of a window: "minimized, maximized, closed, open"

If you need the enumerations to map to values (eg, you need minimized to equal 0, maximised to equal 100, etc) I'd use a hash of symbols to values, like this:

WINDOW_STATES = { :minimized => 0, :maximized => 100 }.freeze

The freeze (like nate says) stops you from breaking things in future by accident.
You can check if something is valid by doing this

WINDOW_STATES.keys.include?(window_state)

Alternatively, if you don't need any values, and just need to check 'membership' then an array is fine

WINDOW_STATES = [:minimized, :maximized].freeze

Use it like this

WINDOW_STATES.include?(window_state)

If your keys are going to be strings (like for example a 'state' field in a RoR app), then you can use an array of strings. I do this ALL THE TIME in many of our rails apps.

WINDOW_STATES = %w(minimized maximized open closed).freeze

This is pretty much what rails validates_inclusion_of validator is purpose built for :-)

Personal Note:

I don't like typing include? all the time, so I have this (it's only complicated because of the .in?(1, 2, 3) case:

class Object

# Lets us write array.include?(x) the other way round
# Also accepts multiple args, so we can do 2.in?( 1,2,3 ) without bothering with arrays
def in?( *args )
# if we have 1 arg, and it is a collection, act as if it were passed as a single value, UNLESS we are an array ourselves.
# The mismatch between checking for respond_to on the args vs checking for self.kind_of?Array is deliberate, otherwise
# arrays of strings break and ranges don't work right
args.length == 1 && args.first.respond_to?(:include?) && !self.kind_of?(Array) ?
args.first.include?( self ) :
args.include?( self )
end
end
end

This lets you type

window_state.in? WINDOW_STATES

How to implement Enums in Ruby?

Two ways. Symbols (:foo notation) or constants (FOO notation).

Symbols are appropriate when you want to enhance readability without littering code with literal strings.

postal_code[:minnesota] = "MN"
postal_code[:new_york] = "NY"

Constants are appropriate when you have an underlying value that is important. Just declare a module to hold your constants and then declare the constants within that.

module Foo
BAR = 1
BAZ = 2
BIZ = 4
end

flags = Foo::BAR | Foo::BAZ # flags = 3

Added 2021-01-17

If you are passing the enum value around (for example, storing it in a database) and you need to be able to translate the value back into the symbol, there's a mashup of both approaches

COMMODITY_TYPE = {
currency: 1,
investment: 2,
}

def commodity_type_string(value)
COMMODITY_TYPE.key(value)
end

COMMODITY_TYPE[:currency]

This approach inspired by andrew-grimm's answer https://stackoverflow.com/a/5332950/13468

I'd also recommend reading through the rest of the answers here since there are a lot of ways to solve this and it really boils down to what it is about the other language's enum that you care about

Ruby - File for Enums

Good question. Simply require the GlobalEnums.rb module at the top of your file and then refer to the module and constant like this:

newGuy.currentContractType = ContractType::Internship

You'll note that in Ruby :: is used to refer to a constant in a namespace (class or module), rather than .. If you have more than one level of nesting, you just chain the ::s:

module Foo
module Bar
class Baz
Qux = "quux"
end
end
end

p Foo::Bar::Baz::Qux
# => "quux"

P.S. I suggest glancing through a Ruby style guide such as this one, in particular the Naming section. With rare exceptions, method and variable names in Ruby are snake_case. Module and class names are CamelCase and other constants are usually SCREAMING_CAMEL_CASE.

With that in mind, a seasoned Rubyist would probably write your code like this:

module MyApp
module ContractType
UNDEFINED = 0
INTERNSHIP = 1
CLT = 2
CONTRACTOR = 4
end
end

# Assuming this is somewhere inside the MyApp namespace...
new_guy.current_contract_type = ContractType::INTERNSHIP

For example, strict conversion methods like Integer(n) and shortcut constructors like URI(str) or Nokogiri::XML(str).

Enums in Rails: uppercase or lowercase?

In Rails enums should be snake_case.

Why? Because enums are used to construct method names. And method names in Ruby should be snake_case according to the community convention. Using ALLCAPS or CamelCase can lead to bugs and confusion as Ruby treats such identifiers as constants.

ActiveRecord::Enum is not comparable to a language level enumeration construct such as in Java.

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

http://api.rubyonrails.org/classes/ActiveRecord/Enum.html

The keys mappings in an ActiveRecord::Enum are not constants. Rather it's just a list which is used with Ruby style metaprogramming that adds methods to make bitmask columns easier (and more fun) to work with.

In your example its actually comparable to:

enum status: [ :ACTIVE, :DRAFT, :INACTIVE ] 

An enum type is a special data type that enables for a variable to be
a set of predefined constants. The variable must be equal to one of
the values that have been predefined for it. Common examples include
compass directions (values of NORTH, SOUTH, EAST, and WEST) and the
days of the week.

https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html

Enums in Java are basically glorified switch statements where the class constants denote the possible values.

enum option new not working

The error clearly states that you cannot have an enum with new key as it will conflict with an existing ActiveRecord method. There is no way out of this.

This issue is not new and it has been discussed before.

I would recommend you to read

enum: Option not to generate "dangerous" class methods

As per Godfrey Chan, Collaborator of Rails:

In this case, if you want to use enum, you are probably better off
renaming your label to something else. This is not unique to enums – a
lot of Active Record features generates methods for you and usually
there aren't ways to opt-out of those generated methods.

Gonna give this one a close for now....

Rails 4 - how to use enum?

You're so close :) The fix is right in the error.

Your select is calling

Preference.self_governance.to_a.map { |p| [p.humanize, p] }

And your error tells you the pluralization is wrong. Remember that if you call enum on a single object, it will be

@preference.self_governance

But if you call on the model itself, Preference, and request a collection it's plural.

Preference.self_governances

Because enum is special, uour enum's could just be arrays, instead of hashes:

enum self_governance: [ tier_1, tier_2, tier_3, tier_4, tier_55 ]

enum autonomy: [ tier_11, tier_21, tier_31, tier_41, tier_51 ]

Your view would look like:

<%= f.input :self_governance, as: :select, label: "Select your governance approach", collection: Preference.self_governances.map { |key, value| [key.humanize, key] } %>

It will store the index number of the array, like magic :)

How to use an enum value in an :if validation

You can evaluate your field using pending?

class Job < ActiveRecord::Base
enum status: [ :active, :archived ]
validates_presence_of :explanation, if: Proc.new { |a| a.extended? }
end

Complete example :

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

# conversation.update! status: 0
conversation.active!
conversation.active? # => true
conversation.status # => "active"

# conversation.update! status: 1
conversation.archived!
conversation.archived? # => true
conversation.status # => "archived"

# conversation.status = 1
conversation.status = "archived"

conversation.status = nil
conversation.status.nil? # => true
conversation.status # => nil

Reference:
http://api.rubyonrails.org/classes/ActiveRecord/Enum.html

Rails 4 enum - how to test equality?

@user.domain_admin? # return true if :domain_admin

instead:

@user.role == :domain_admin

use:

@user.role == "domain_admin"

Some test:

=> User.roles
=> {"user"=>0, "staff"=>1, "admin"=>2}
=> u = User.last
=> u.role
=> "user"
=> u.role == "user" # <-- this
=> true
=> User.roles.each_pair { |x, _| puts u.role == x }
=> true
=> false
=> false


Related Topics



Leave a reply



Submit