views:

4693

answers:

9

I would like to create an enum field at sone migration I'm doing, I tried searching in google but I can't find the way to do it in the migration

the only thing I found was

  t.column :status, :enum, :limit => [:accepted, :cancelled, :pending]

but looks like the above code runs only on rails 1.xxx and since I'm running rails 2.0

this what I tried but it fails

class CreatePayments < ActiveRecord::Migration
  def self.up
    create_table :payments do |t|
      t.string :concept
      t.integer :user_id
      t.text :notes
      t.enum :status, :limit => [:accepted, :cancelled, :pending]

      t.timestamps
    end
  end

  def self.down
    drop_table :payments
  end
end

So, in case that isn't allowed, what do you think could be a good solution? just a text field, and validating from the model?

+2  A: 

Add the following:

module ActiveRecord
  module ConnectionAdapters #:nodoc:
    class TableDefinition
      def enum(*args)
        options = args.extract_options!
        column_names = args

        column_names.each { |name| column(name, 'enum', options) }
      end
    end
  end
end

to lib/enum/table_definition.rb and include it in your init.rb.

Banang
nice piece of code, I will try to implement it, question, this looks easy an nice, so why rails doesn't include it in the core?
Gabriel Sosa
Probably because not all databases support ENUMs.
wesgarrison
+1  A: 

I have dozens of these little enums, with 3-300 entries in each. I implement them as lookup tables. I don't have a model file for each one; I use some metaprogramming to generate a model for each, since each table has the same set of columns (id, name, description).

Since some of the sets had enough elements to warrant their own table, it was more consistent to move them all to tables. Just another option if you'll have more of these enums later.

EDIT: Here's how I generate the models:

ACTIVE_RECORD_ENUMS = %w{
  AccountState
  ClientType
  Country
  # ...
}

ACTIVE_RECORD_ENUMS.each do |klass|
  eval "class #{klass} < ActiveRecord::Base; end"
  klass.constantize.class_eval do
    class << self

      def id_for(name)
        ids[name.to_s.strip.humanize.downcase]
      end

      def value_for(id)
        values[id.to_i]
      end

      def values
        @values ||= find(:all).inject({}) {|h,m| h[m.send(primary_key)] = m.name; h}
      end

      def ids
        @ids ||= self.values.inject({}) {|h, {k, v}| h[v.downcase] = k; h}
      end

    end
  end
end

This file lives in the models directory, and is included in application_config.rb. This lets me do stuff like this:

AccountState.ids 
# => {"active" => 1, "deleted" => 2}
AccountState.values 
# => {1 => "Active", 2 => "Deleted"}
AccountState.id_for("Active") 
# => 1
AccountState.value_for(1) 
# => "active"
Sarah Mei
I'd be very interested to see the code or hear more about the metaprogramming you're doing to accomplish this if it's not too much trouble.
Adam Alexander
Edited to add some code. I haven't looked at it in a while and found I had to look up (again) what inject does. Perhaps that's an argument for using collect instead...
Sarah Mei
Very nifty idea. I have lots of those lookup tables that I never want to make model files for.
wesgarrison
A: 

ok, just read the whole rails api and found what I neeed and I dont like :( Rails doesn't support emum as native type on migrations, here is the info, I need to search for a plugin or other method.

I will keep you posted.

Gabriel Sosa
IIRC, rails only supports features that are implemented by all the supported databases, so there's no "out of the box" enum support.
MarkusQ
A: 

Another option: drop to SQL.

def self.up
  execute "ALTER TABLE `payments` ADD `status` ENUM('accepted', 'cancelled', 'pending')"
end
wesgarrison
+2  A: 

Have you looked at the enum-column plugin on RubyForge?

+9  A: 

Look at tip #3 on http://zargony.com/2008/04/28/five-tips-for-developing-rails-applications

This exactly what you need!

 class User < ActiveRecord::Base
   validates_inclusion_of :status, :in => [:active, :inactive]

   def status
     read_attribute(:status).to_sym
   end

   def status= (value)
     write_attribute(:status, value.to_s)
   end
 end

HTH

Pavel Nikolov
+3  A: 

You can manually specify the type by using the t.column method instead. Rails will interpret this as a string column, and you can simply add a validator to the model like Pavel suggested:

class CreatePayments < ActiveRecord::Migration
  def self.up
    create_table :payments do |t|
      t.string :concept
      t.integer :user_id
      t.text :notes
      t.column :status, "ENUM('accepted', 'cancelled', 'pending')"

      t.timestamps
    end    
  end

  def self.down
    drop_table :payments
  end
end

class Payment < ActiveRecord::Base
  validates_inclusion_of :status, :in => %w(accepted cancelled pending)
end
Adam Lassek
+1: it's the most simple solution
MickTaiwan
+3  A: 

Similarly, the enumerated_attribute gem manages enums at the object level.

enum_attr :status, %w(accepted cancelled ^pending)

Define a string in the migration

t.string :status

Also provides some nice features like dynamic predicate methods.

http://github.com/jeffp/enumerated_attribute/tree/master

+3  A: 

You can try the (very) comprehensive jeff's enumerated_attribute gem OR go with this simple workaround:

class Person < ActiveRecord::Base
  SEX = [:male, :female]

  def sex
    SEX[read_attribute(:sex)]
  end

  def sex=(value)
    write_attribute(:sex, SEX.index(value))
  end
end

And then declare the sex attribute as an integer:

t.integer :sex

This worked very fine for me! =D

Lailson Bandeira