How do I add a new join table for two existing tables in Rails?

Viewed 47

My current association looks like this:

class Location < ApplicationRecord
  has_many :instructors
end

class Instructor < AppliactionRecord
  belongs_to :location
end

I want to introduce a new location_instructors table that joins the above tables. Ideally, my association would turn into this:

class Location < ApplicationRecord
  has_many :location_instructors
  has_many :instructors, through: :location_instructors
end

class LocationInstructor < ApplicationRecord
  belongs_to :location
  belongs_to :instructor
end

class Instructor < ApplicationRecord
  has_many :location_instructors
  has_many :locations, through: :location_instructors
end

My main concern is that dropping the location_id column on the instructors table will make Rails not know which instructor belongs to which location anymore. How would I approach this new migration to make sure that there are location_instructors records for existing locations and instructors?

3 Answers
  1. Create the join table.
  2. Write and run a rake task to run through the instructors and add a record for each in the new join table.
  3. Create a migration to remove the location_id.
task make_instructor_joins: :environment do
  Instructor.all.each do |instructor|
    LocationInstructor.create(
      instructor_id: instructor.id,
      location_id: instructor.location_id
    )
  end
end

Here is how you can do everything in one single migration (the same you will use to create the join table):

def change

  create_join_table :locations, :instructors do |t|
       t.index [:location_id, :instructor_id]
       t.index [:instructor_id, :location_id]
     end
  
  Instructor.find_in_batches do |instructors_group_i|
    instructors_group_i.each do |instructor_i|
       LocationsInstructors.create!(location_id: instructor_i.location_id, instructor_id: instructor_i.id)
    end
  end
  remove_column :instructors, :location_id
end

A quick explanation of the snippet above: Basically that migration creates a new join table and then seed the table with the 'existing' associations, I advise you to use find_in_batches instead of all since this will load only 1000 (default value you can change it if you want) of records instead of the full collection and that can be problematic to deal with if your data set is big enough (you can run out of memory). The last step that is pretty clear is just to remove the old column. (You should run any operations first against a test dataset).

Hope this helps!

Related