Cliff Says

Here’s a stab at it. Generate a PaymentType model with a single string field.

rails generate model payment_type name:string

Add the three payment types in seeds.rb, and run rake db:seed.

PaymentType.create(:name => "Check")
PaymentType.create(:name => "Credit card")
PaymentType.create(:name => "Purchase order")

Add a names class method to the PaymentType model.

class PaymentType < ActiveRecord::Base
  def self.names
    all.collect { |payment_type| payment_type.name }
  end
end

Change the validation in the Order model. Initially, one might do this:

validates :pay_type, :inclusion => PaymentType.names

Now I’m a Rails n00b, but as far as I can tell, doing validation that way would fetch the payment types just once from the DB at the moment when Ruby defines the Order model class. What if the payment types table in the DB is modified after the Order model class has been defined? Hence, I rewrote the validation to query the DB for the payment types every time the validation is called (at least, I think I did):

validates_each :pay_type do |model, attr, value|
  if !PaymentType.names.include?(value)
    model.errors.add(attr, "Payment type not on the list") 
  end
end
Then, in views/orders/_form.html.erb, replace
<%= f.select :pay_type, Order::PAYMENT_TYPES,
             :prompt => 'Select a payment method' %>

with

<%= f.select :pay_type, PaymentType.names,
             :prompt => 'Select a payment method' %>

Wham bam. Added some unit and functional tests too. Works fine. I’m not super satisfied with the messy-looking validates_each block, though. Anyone with a more elegant validation?

Older says (11/09/08)

Probably the best way to avoid later problems in case there is some payment method chances, would be to link an order’s payment method to the payment method’s id. This way, we can always change payment method names without having to update older orders. Then, it would be nice to migrate and add payment_method.id to order. Edit: I tried to but did not go well. I guess there is a way to deal with catalog table I don’t know yet.

Ernesto says (14/09/2011)


rails generate migration add_pay_type_id_to_order pay_type_id:integer
rails generate scaffold pay_type name:string
rake db:migrate
than:

order.rb
  has_one :pay_type
  validates :pay_type_id, presence: true

pay_type.rb
  belongs_to :orde

in app/views/orders/_form.html.erb
<div class="field">
    <%= f.label :pay_type_id %><br />
    <%= collection_select(:order, :pay_type_id, PayType.all, :id, :name) %>  
</div>

You also need to edit the other views in app/views/orders….

more informations about: http://guides.rubyonrails.org/form_helpers.html starting at 3.2

Pepe says (26/09/2011)

Ernesto solution have some errors in order and pay_type models using has_one and belongs_to rails methods.

belongs_to means that the foreign key is in the table for this class. So belongs_to can ONLY go in the class that holds the foreign key.

has_one means that there is a foreign key in another table that references this class. So has_one can ONLY go in a class that is referenced by a column in another table.

So correct setting are:


order.rb
  belongs_to :pay_type
  validates :pay_type_id, presence: true

pay_type.rb
  has_many :order

In app/views/orders/index.html.rb
  <td><%= order.pay_type.name %></td>

Diego says (28/05/2012)

Well I also implemented migration code that creates the new PaymentType table and migrates all the existing data using the new data model.


 def up
    create_table :payment_types do |t|
      t.string :name

      t.timestamps
    end

    PaymentType.create(name: 'Check')
    PaymentType.create(name: 'Credit Card')
    PaymentType.create(name: 'Purchase Order')

    add_column :orders, :payment_type_id, :integer
    Order.reset_column_information
    Order.all.each do |order|
      order.payment_type_id = case order.pay_type
                              when 'Check'
                                1
                              when 'Credit Card'
                                2
                              when 'Purchase Order'
                                3
                              end
      order.save validate: false
    end
    Order.reset_column_information
    remove_column :orders, :pay_type
  end

  def down
    add_column :orders, :pay_type, :string
    Order.reset_column_information
    Order.all.each do |order|
      order.pay_type = case order.payment_type_id
                       when 1
                         'Check'
                       when 2
                         'Credit Card'
                       when 3
                         'Purchase Order'
                       else
                         'Credit Card'
                       end
      order.payment_type_id = 0
      order.save validate: false
    end
    Order.reset_column_information
    drop_table :payment_types
    remove_column :orders, :payment_type_id
  end

Not the best code in the earth but it was a good opportunity to try some things using ruby :)