For a Rails 3.0 Todo app, I have a Tasks model with a Status field. What\'s the best way to store the Status field data (field type) and still display a human-r
I prefer to store "normal", "active", .. "completed" as string in the table because:
These days, I tend to decouple Rails constants from the database as much as I can. There are always some PHP/MSSQL/??DBA folks around us (who may not love Rails as much as we do ;-)
So, the answer is not integer nor enum (but a varchar ;-)
I have used Enum-Column for such use cases. The plugin allows you to define a enum column type in your migration script and creates a MYSQL enum column type for the attribute.
create_table :tasks do |t|
...
t.enum :status, :limit => [:normal, :active, :completed], :default => :normal
...
end
Now in your code you can do the following:
task.status = "active"
task.status = :completed
p "Task status: #{task.status}" # prints Task status: completed
Task.find_by_status(:active)
Task.find_by_status("active")
Task.find_by_status(2)
Works well with serialization too:
task.to_xml # will result in status= active instead of status-2
Other nice aspect is, the status values are displayed as strings when viewed using a regular DB client(E.g: mysql command console or phpMyAdmin)
The plugin provides optimal storage and user friendly access for the enumeration types.
Caveat:
The plugin is quite old and not maintained. I am using it extensively with MySQL DB. I had to patch the code to get it work on PostgreSQL. If you are using MySQL, this plugin is a good choice.
Using integer to store the status is a good idea. Nonetheless, I think the code provided by the accepted answer is not elegant since it pollutes the whole model class just because of an attribute.
My suggestion is overriding the attribute getter:
def status
{
0 => "active",
1 => "inactive"
}[read_attribute(:status)] # use the read_attribute method to prevent infinite loop.
end
The logic of transforming the integer into a string will be only in this getter method, so you don't need to make the class dirty.
The easiest thing to do would be to just store the actual strings in the field instead of adding another table. Depending on your point of view this is either a bad idea as your database will not be sufficiently normalized or a great idea as your app is now more efficient for being normalized.
If you opt to not do that and to keep the values in a separate table; you need to setup the relationships in the model.
class Task < ActiveRecord::Base
has_one :status
end
class Status < ActiveRecord::Base
belongs_to :tasks
end
Then in your view you can reference the value by:
<%= task.status %>
I know this is an old question but I wanted to mention 2 points that come from experience, especially if someone is looking for this now ( 2014 - OQ was in 2010) :
1.It depends on how much you want to optimize queries on the DB.
2.Not really, it is not supported 'out of the box' by AR. # As of Rails 4 enums are supported out of the box.
3.IMHO you can use strings without a big performance penalty (just remember to add field to an index). I would do this because it's easier to internationalize and to maintain. However, you can go with integers if you need extra performance.
You may take a look on 2 SO threads here and here where this is debated.
4.If you want to keep them as integer, here is how you can accomplish this:
class Task << AR::Base
NORMAL = 1
ACTIVE = 2
COMPLETED = 3
STATUSES = {
NORMAL => 'normal',
ACTIVE => 'active',
COMPLETED => 'completed'
}
validates_inclusion_of :status, :in => STATUSES.keys,
:message => "{{value}} must be in #{STATUSES.values.join ','}"
# just a helper method for the view
def status_name
STATUSES[status]
end
end
and in view:
<td><%= task.status_name %></td>
If you want to use strings, it's more simplified:
STATUSES = ['normal', 'active', 'completed']
validates_inclusion_of :status, :in => STATUSES,
:message => "{{value}} must be in #{STATUSES.join ','}"