Search code examples
ruby-on-railsrubyenums

Rails: Enums with hashes not returning hash key:value


I am building a Ruby on Rails Application where I have model attributes that are static like Gender and Status. I have decided to define them as enums in the models. I am using the ruby-enum gem to define the enums.

I have added the ruby-enum gem to my project, and ran bundle install:

gem 'ruby-enum', '~> 0.8.0'

However, the enums needs to be accessible by various other models, so I have defined the enums as modules in the concerns directory of my models:

# app/models/concerns/status.rb

module Status
  extend ActiveSupport::Concern
  included do
    include Ruby::Enum

    define :ordered, 'Ordered'
    define :cancelled, 'Cancelled'
    define :waiting, 'Waiting'
  end
end

I have included this module in my Users model

class User < ApplicationRecord
  include Status
end

And my Users model has a column for status:

create_table "users", force: :cascade do |t|
  t.string "first_name"
  t.string "last_name"
  t.string "status"
  t.datetime "created_at", precision: 6, null: false
  t.datetime "updated_at", precision: 6, null: false
end

But when I query the Status module in the rails console:

Status.all

I get the error:

NoMethodError (undefined method `all' for Status:Module)

When I also query the User Model in the rails console for all statuses:

User.statuses.all

I get the error:

ArgumentError (wrong number of arguments (given 2, expected 0..1))

I also don't know how to make this available in the views

  # app/views/users/_form.html.erb

  <%= form.label :status %><br />
  <%= form.select :status, User.statuses.keys.collect { |status| status },{} %>  

How can I go about defining the enums and also returning the key values of the enums in the users' form view so that a status can be selected in the form?


Solution

  • But when I query the Status module in the rails console:

    Status.all
    

    I get the error:

    NoMethodError (undefined method `all' for Status:Module)
    

    .all asks for all the records of an ActiveRecord class. Status is not an ActiveRecord class. It isn't even a class, it is a module.

    As for User.statuses.all, I don't see where statuses is defined.


    Ruby::Enum has added some constants and methods, but Rails doesn't know about them. You'd have to integrate them into Rails yourself.

    Rails already has enums which are integrated.

    module Status
      extend ActiveSupport::Concern
      included do
        # It will take an Array, but it's good practice to be explicit.
        enum status: {ordered: 0, cancelled: 1, waiting: 2}
      end
    end
    

    User.statuses will return a HashWithIndifferentAccess of your statuses.

    Note that these enums are mapped to integers, strings defeat the point of an enum. Storing your status as an integer will save you potentially a lot of space. Rails will handle mapping the integers into strings and back for you.

    Be sure to change your table to use integer status: t.string "status". To avoid Users with on status, you will want to define either a default or a null: false.


    I want to see Order and not order in the views page. I want to see Not ordered and not not_ordered in the views page. I also want the values of the status to be stored in the database and not the integers like 0, 1, 2. – Promise Preston 12 secs ago

    Details of how data is displayed does not belong in the database. Simplest thing is to use humanize.

    # ordered becomes Ordered. not_waiting becomes Not waiting.
    User.statuses.keys.collect { |status| status.humanize }
    

    You can push that off into the Status module.

    module Status
      ...
    
      class_methods do
        def show_statuses
          statuses.keys.collect { |status| status.humanize }
        end
      end
    end
    
    User.show_statuses
    

    Doing this as a select...

    <%= f.select :status, User.statuses.collect { |status,id| [status.humanize,id] } %>
    

    And you can also push that into Status.

    module Status
      ...
    
      class_methods do
        ...
    
        def statuses_for_select
          statuses.collect { |status,id| [status.humanize,id] }
        end
      end
    end
    
    <%= f.select :status, User.statuses_for_select %>
    

    As your display details become more complicated your models will get fat. Then you'll want to consider pushing it off into a decorator.