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?
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.