37

How do I create a migration with two fields that reference the same table? I have tables A, and image. A.image1_id will reference image, and A.image2_id will reference image also. There are only 2 images, not many. If I use

class AddFields < ActiveRecord::Migration
   def change
    change_table(:ticket) do |t|
        t.references :image1_id
        t.references :image2_id
    end
  end
end

I don't think that will work because it will add another _id to the end and probably won't know to use the 'image' model. I also thought about

change_table(:ticket) do |t|
    t.references :image

But then how do I add two of those? I also thought about adding

create_table :images do |t|
  t.belongs_to :ticket
  t.string :file

But I only want 2, not many, and this doesn't appear to allow getting to the image from the ticket, like ticket.image1 or ticket.image2.

According to this documentation http://apidock.com/rails/v3.2.8/ActiveRecord/ConnectionAdapters/SchemaStatements/change_table which is all I could find, t.references doesn't appear to take any arguments either.

change_table(:suppliers) do |t|
  t.references :company
end
tereško
  • 58,060
  • 25
  • 98
  • 150
Chloe
  • 25,162
  • 40
  • 190
  • 357
  • I think now I would just create one relation and have a `before_save` filter or `validate :my_validation` to limit the relation to 2 records. – Chloe Jun 02 '14 at 22:56

2 Answers2

45

You can do this simply with the add_column method in your migrations and set up the proper associations in your classes:

class AddFields < ActiveRecord::Migration
  def change
    add_column :tickets, :image_1_id, :integer
    add_column :tickets, :image_2_id, :integer
  end
end

class Ticket < ActiveRecord::Base
  belongs_to :image_1, :class_name => "Image"
  belongs_to :image_2, :class_name => "Image"
end

class Image < ActiveRecord::Base
  has_many :primary_tickets, :class_name => "Ticket", :foreign_key => "image_1_id"
  has_many :secondary_tickets, :class_name => "Ticket", :foreign_key => "image_2_id"
end

This blog post, Creating Multiple Associations with the Same Table, goes into more detail.

rossta
  • 11,394
  • 1
  • 43
  • 47
  • 4
    Right, `t.references :x` is just shorthand for `t.column :x_id, :integer` or `t.integer :x_id`. – mu is too short Feb 14 '13 at 05:19
  • So I don't need t.references to be able to use `ticket.image1` ? – Chloe Feb 14 '13 at 05:20
  • Like @muistooshort said, it's a convenience method. Yours is a special case where it's probably better to use a different helper method. – rossta Feb 14 '13 at 05:22
  • 3
    Wow `belongs_to` and `has_one` is confusing. It sounds backwards. The ticket 'references' the image, and the image 'belongs to' the ticket. – Chloe Feb 14 '13 at 06:18
  • `t.references` also adds the `index: true` argument to the `add_column` method, which adds an index – sixty4bit Oct 06 '15 at 19:01
  • @sixty4bit would adding the index in this case of having two fks be a good thing or a potentially harmful thing? – michaelsking1993 Mar 10 '17 at 06:49
  • `t.references` also allows you to specify `foreign_key` (see my answer https://stackoverflow.com/a/57322220/2871922) enabling relational integrity at the database level, so the DB want accept any old integer there, but only a primary key of the linked table. – Toby 1 Kenobi Mar 24 '21 at 23:20
  • @michaelsking1993 adding the index is definitely a good thing. Every FK should be indexed to avoid making table joins painfully slow. It wont confuse the database to have two indexed foreign keys to the same table. – Toby 1 Kenobi Mar 24 '21 at 23:23
29

In Rails 5.1 or greater you can do it like this:

Migration

class AddFields < ActiveRecord::Migration
   def change
    change_table(:tickets) do |t|
        t.references :image1, foreign_key: { to_table: 'images' }
        t.references :image2, foreign_key: { to_table: 'images' }
    end
  end
end

This will create the fields image1_id, and image2_id and make the database level references to the images table

Models

as in rossta's asnwer

class Ticket < ActiveRecord::Base
  belongs_to :image1, class_name: "Image"
  belongs_to :image2, class_name: "Image"
end

class Image < ActiveRecord::Base
  has_many :primary_tickets, class_name: "Ticket", foreign_key: "image1_id"
  has_many :secondary_tickets, class_name: "Ticket", foreign_key: "image2_id"
end

FactoryBot

If you uses FactoryBot then your factory might look something like this:

FactoryBot.define do
  factory :ticket do
    association :image1, factory: :image
    association :image2, factory: :image
  end
end
Toby 1 Kenobi
  • 4,717
  • 2
  • 29
  • 43