March 31, 2015 by Daniel P. Clark

Rails 4’s Awesome enums

In Rails 4 enum was introduced to support a feature that people were building by hand or by gem.  What enum does for you is it associates a symbol list for you to reference which will be stored in the database as an integer.  So :cow could be 0 and :dog could be 1 in an enum of [:cow, :dog] .  But enum is far more helpful that that.  It builds scopes and boolean checker methods.

For example.  Lets create a Flower object and we’ll have kinds and colors as enums.

First we generate the model.

rails g model Flower kind:integer:index color:integer:index

Then we update app/models/flower.rb

# app/models/flower.rb
class Flower < ActiveRecord::Base
  enum kind:  [:rose, :tulip]
  enum color: [:blue, :red, :yellow, :white]
end

Make sure you migrate.

rake db:migrate

And now all the hard work has been done for you.

You can create a blue rose in two different ways.  With scopes, or with attributes.

# By Scopes
Flower.blue.rose.create

# By Attributes
Flower.create(kind: Flower.kinds[:rose], color: Flower.colors[:blue])

As you can see I used the plural name on the class of Flower for setting the attribute values.  The plural named method is a Hash of the enum symbol names to the index of their placement in the originally defined Array.  So kinds[:rose] will return 0 for the attribute to be written to the database since it was the first item in the enum defined.

And you can access them by the same scopes (and scopes work in any order)

Flower.rose.blue.count
# => 2

As I had mentioned earlier enum will add boolean checkers for every single enum option.

Flower.first.tulip?
# => false 
Flower.first.rose?
# => true 
Flower.first.yellow?
# => false 
Flower.first.blue?
# => true

And you can forcefully change them with a bang method.

Flower.first.color
# "blue"
Flower.first.blue?
# => true
Flower.first.yellow!
Flower.first.color
# "yellow"
Flower.first.yellow?
# => true
Flower.first.blue?
# => false

If you look at the record’s attributes you can see that the enum‘s are stored as integers.  The integers are the index of the Array in which you declared the enum.  So you can add new items to the end of the enum Array, but don’t change the ordering.

Flower.first.attributes
# => {"id"=>1, "kind"=>0, "color"=>2,"created_at"=>...,"updated_at"=>...}

To check whether an enum value has been set for any of the enum fields just use a boolean check on the enum name.

Flower.first.color?
# => true
Flower.first.kind?
# => true

Summary

As you can see enum has saved us a lot of work by defining many scopes and helper methods.  This comes in to be pretty handy.  As a consequence though you can’t use conflicting names between any enum or ActiveRecord methods.  So you may have to be clever for some names to use.  I really like having the convenience that enum adds.  And now you have this power tool in your tool set! ^_^

Please share, I’d love to hear about them.  Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by Hernán García Crespo via the Creative Commons Attribution 2.0 Generic License.

#activerecord#boolean#database#db#enum#helper#rails#rails 4#scope#scopes

Comments

  1. MrChris
    April 1, 2015 - 6:57 am

    “but don’t change the ordering.”

    Bugs be here!

    • fritzhut
      April 1, 2015 - 7:18 am

      Yea the gem ‘enumerize’ is way better and provides the same scope/predicate helper methods. I think this is one of the most overhyped features of rails with the worst implementation

      • Daniel P. Clark
        April 1, 2015 - 2:15 pm

        How do you believe it’s the “worst implementation”?

    • Daniel P. Clark
      April 1, 2015 - 2:16 pm

      @MrChris:disqus It’s a rookie mistake. But it’ll happen if you don’t know any better.

      • MrChris
        April 1, 2015 - 2:43 pm

        Are your saying that changing the order is a rookie mistake? It’s not. Changing the order of an enumeration shouldn’t affect anything.

        The rookie mistake is writing code that easily breakable in the first place.

        • Daniel P. Clark
          April 1, 2015 - 4:07 pm

          That’s an interesting opinion. In the case of Arrays; accessing by index will always break if the order of the Array changes. So in the same sense this is just an Array (which it is) and the records are the indexes (which they are).

          It’s not a “bad” implementation. It’s just “a way” to implement it. One could argue that any code is easily breakable if one doesn’t know what one is doing. So I don’t see why you’d consider it a mistake.

          • MrChris
            April 1, 2015 - 4:40 pm

            So, when adding a new item to the enumeration do you add it to the end or the start of the enumeration array? The only way to know the answer is to know how the underlying implementation of enumeration works. That is just plain wrong and bad code. It’s brittle. Should you expect every new developer on the project to know this small implementation detail? Clearly not. Adding an item to the start of the array will completely break your software.

          • Daniel P. Clark
            April 1, 2015 - 4:54 pm

            I guess it depends on the situation. A great way to enforce the order is to write a test saying the enum Array must start with these items [:a,:b,:c…] That way if the enum gets changed at the wrong end, the test fails, and the new developer can see the explanation in the test (with a note an order importance). If you have a concern of what new developers may do, the tests should account for that. Again “anything can break”, regardless of enum, based on who’s changing what.

  2. Ben Greville
    April 5, 2015 - 6:01 pm

    Active enums are all well and good when your rails stack is the only level of business logic “dressing” your data.
    However when other business applications like a data warehouse need to process this raw data, they will most likely not have access to the active enum information defined in your Rails model.
    In this scenario it’s better to have that information defined inside the database tier of your application.
    That way the business definition of what colours a flower can be doesn’t have to be duplicated in both Ruby and your DW.

    • Daniel P. Clark
      April 6, 2015 - 8:05 pm

      You raise an interesting point. Are you familiar with any external database processors that don’t require a schema? The few I know of you still specify at least a basic schema (such as Googles big data). Haven’t looked at how they’d handle enums.

      I know that Postgres itself supports enum and has the information of what each enum item is in the DB. I don’t believe Rails ties in with that as Rails’ is DB impartial. I wonder what kind of hack can be implemented to integrate Postgres’ enum with Rails’ to save the field information.

      I’m also curious as to efficiency and capacity of enums over string value storage. If I were to store insane amounts of data I would think simple integers for enums would be more efficient in both senses. Although I haven’t looked into this myself so I don’t truly know. I believe that most people don’t consider something like this as a general concern since systems have overall high capacities these days.

  3. Francisco Quintero
    June 24, 2015 - 10:34 am

    Hi!

    This post helped me test my enums in the console.

    I’m using enums to fill some select inputs, however, if I use the symbol notation(:dog, :cow), the option is printed as it is written in the enum declaration.

    To solve that, I declare my enums as strings(‘Dog’, ‘Cow’) and it works normal but when testing in the rails console whether they really worked I have to create them passing the exact name:

    Animal.create(type: Animal.types[‘Dog’]) in order to get the record save.

    Animal.create(type: Animal.types[:dog]) or Animal.create(type: Animal.types[:Dog]) are not available.

    Could this cause any trouble later? Is there a better way to declare a good-looking enum using symbol notation?

    Thanks anyway and great post 🙂

    • Daniel P. Clark
      July 6, 2015 - 4:19 pm

      Capitalized strings seems to be an issue for the view. So I’d advise using symbols behind the scenes and in the views simply map capitalize Animal.types.map(&:capitalize)

      • Francisco Quintero
        July 11, 2015 - 1:29 pm

        Will give it a shot. Thanks again.

      • Francisco Quintero
        October 24, 2015 - 9:57 pm

        I finally worked this around. Created my enums using symbols and in views they’re displayed calling .humanize or .titleize methods.

Leave a Reply

Your email address will not be published / Required fields are marked *