开发者

In Rails, how should I implement a Status field for a Tasks app - integer or enum?

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-readable version in a view (HTML table)? Status can be:

0 = Normal

1 = Active

2 = Completed

Right now I have this:

Rails Schema Here:

create_table "tasks", :force => true do |t|

t.integer "status", :limit => 1, :default => 0, :null => false

Rails Model Here:

class Task < ActiveRecord::Base
  validates_inclusion_of :status, :in => 0..2,
    :message => "{{value}} must be 0, 1, or 2"

Rails View Here:

<h1>Listing tasks</h1>

<table>
  <tr>
    <th>Status</th>
    <th>Name</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>

<% @tasks.each do |task| %>
  <tr>
    <td><%= task.status %></td>
    <td><%= task.name %></td>
开发者_JAVA百科    <td><%= link_to 'Show', task %></td>
    <td><%= link_to 'Edit', edit_task_path(task) %></td>
    <td><%= link_to 'Delete', task, :confirm => 'Are you sure?', :method => :delete %></td>
  </tr>
<% end %>
</table>

Requirements

  1. Store a Task's status in the db such that the values are easily localizable, i.e. I'm not sure I want to store "normal", "active", "completed" as a string field.

  2. Solution must work with Rails 3.0.

Questions:

  1. Should I store the field as an integer (see above)? If so, how do I display the correct human readable status in an HTML table in my Rails view, e.g. show "Active" instead of "1" in the HTML table.

  2. Should I use an enum? If so, is this easy to localize later?

  3. Should I use straight strings, e.g. "Normal", "Active", "Completed"

  4. Can you provide a quick code sample of the view helper, controller or view code to make this work?


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 ','}"


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


I prefer to store "normal", "active", .. "completed" as string in the table because:

  • it's self documentation (for example, someone may look at the data but never read the Rails source code)
  • there is little (if not no) performance penalty
  • it is (still) easy to do i18n by means of Rails virtual attribute in the model or whatever layer in other languages

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 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. If you are starting a new project > Rails 4 ( technically ActiveRecord 4) - use Enums - the most efficient route. Especially if you need to create any kind of complicated SQL queries later on.
  2. There is on more alternative - create a composite Status model that will hold statuses for all your other models. Make it an STI model (add type column)- then you can create things like OrderStatus < Status or CustomerStatus < Status and your Order and Customer would have status_id attributes. This makes for slower (!) and more cumbersome (!) queries, however you might want to go this route if you are a creating an app that will be shipped to client that has no technical expertise and they would need some kind of ability to add / remove statuses through something like ActiveAdmin on their own rather than modify your code base. This is also an option if your data layer can't handle enums.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜