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

拥有回忆 提交于 2019-12-02 14:18:20
Vlad Zloteanu

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.

ohho

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!