Reputation: 24573
I am trying to figure out what is the best way to store an enum value in activerecord but convert it to a 'title' for display in an app.
I.E.
Review Enum:
UNREVIEWED = {:title => "Unreviewed", :name => "UNREVIEWED"}
REVIEWED = {:title => "Reviewed", :name => "REVIEWED"}
FLAGGED = {:title => "Flagged as inappropriate", :name => "FLAGGED"}
So in java land I was used to storing the ENUMs name ie (REVIEWED) in the database and then converting that name into that actual enum on the server such that I could call helper methods on it, ie:
review = Review.valueOf(review)
review.title()
Is there something similar I can do in rails to accomplish this?
FYI we are trying to keep our app super small so if I can easily accomplish this or something similar without a GEM that would be great.
Any 'standard' way to do this, as I imagine I am not the first to struggle with this issue?
Thanks!
Upvotes: 13
Views: 20091
Reputation: 8252
I agree with @tamersalama. Here's a simple way to do it using strings in the DB with a custom Enum class. Note that this also supports human readable names for use in dropdown menus.
https://gist.github.com/alexch/a7be54e1b085718473ff
SQL:
Table name: snacks
id :integer
ice_cream :string
Rails:
class Snack < ActiveRecord::Base
FLAVORS = Enum.new [
[:vanilla, "Vanilla"],
[:chocolate, "Chocolate"],
[:rocky_road, "Rocky Road"]
])
]
end
HTML:
<%= simple_form_for @snack do |f| %>
<%= f.collection_select :ice_cream, Snack::FLAVORS, :value, :label %>
<% end %>
Upvotes: 0
Reputation: 65
Using @Godsaur example.
class MyModel < ActiveRecord::Base
enum status: [:draft, :beta, :public]
end
You can get the string value as:
MyModel.last.status
=> "beta"
But if you want a "title" you can:
MyModel.last.status.titleize
=> "Beta"
Upvotes: 5
Reputation: 4133
While I agree that @Godsaur answer is correct - I personally don't like the approach of storing integer values to represent meaningful string equivalents (provided that enough database indexing is made and/or the cost of querying strings in the DB engine is similar to that of querying integers).
My approach is usually storing the text values in the database (for easier understanding of DB records).
class MyModel < ActiveRecord::Base
STATUSES = HashWithIndifferentAccess.new({unverified: 'unverified', reviewed: 'reviewed', flagged: 'flagged'})
#the text values are similar to their symbols key
#you could also define additional attributes for each key if needed. Ex:
#STATUSES = {unverified: {title: 'Unverified Text Title', txt: 'unverified'}, ...}
# assuming a 'status' field
scope :unverified, -> { where(status: STATUSES[:unverified]) }
def unverified?
status == STATUSES[:unverified]
end
# Or
STATUSES.each do |sym, val|
define_method("#{sym}?") {status == val}
end
end
<%= MyModel::STATUSES[@my_model.status] %> or
<%= MyModel::STATUSES[@my_model.status].title %>
Upvotes: 1
Reputation: 12563
ActiveRecord enums is the best way to go since it's a part of the framework (since version 4.1).
Its usage is quite simple:
Migration:
class AddEnumToMyModel < ActiveRecord::Migration
def change
add_column :my_model, :status, :integer, default: 0
end
end
Model:
class MyModel < ActiveRecord::Base
enum status: [:draft, :beta, :public]
end
Then use it a will:
MyModel.draft # gets all drafts
MyModel.last.draft? # checks if the last model is draft
MyModel.last.status # gets the string description of the status of my model
For mode information refer to documentation.
Upvotes: 29
Reputation: 18835
there are a lot of posts about this issue, i guess that this points to most of them: http://thinkinginrails.com/2010/04/using-enums-for-constants/
i think that this is an over engineered thing, that you don't need in a dynamically typed language like ruby.
just use strings!
you could then use that like:
class Review < ActiveRecord::Base
validates_inclusion_of :status, :in => ["UNREVIEWED", "REVIEWED", "FLAGGED"]
def status
read_attribute(:status)
end
def status= (value)
write_attribute(:status, value)
end
end
Upvotes: 6