Search code examples
ruby-on-railsdata-migrationruby-on-rails-6rails-migrations

How can I migrate a has_many / belongs_to relationship in rails to has_and_belongs_to_many?


I have an existing rails 6 application where I have two models:

class Reservation << ApplicationRecord
# ...
  has_many :charges
# ...
end
class Charge << ApplicationRecord
# ...
  belongs_to :reservation
# ...
end

I want to refactor it to this:

class Reservation << ApplicationRecord
# ...
  has_and_belongs_to_many :charges
# ...
end
class Charge << ApplicationRecord
# ...
  has_and_belongs_to_many :reservation
# ...
end

What I want to know is how to write that migration? There's already data in the table, so I need to retain existing charges whose reservation IDs are set and keep the link.


Solution

  • Be careful here, and make sure you can revert if there's a mistake, so you don't lose your data!

    First you need to create the join table with a migration. You can create the migration from the command-line with:

    rails g migration create_charges_reservations
    

    this should create the template of the migration for you in db/migrate, which you'll populate according to your need like this:

    class CreateChargesReservations < ActiveRecord::Migration[6.0]
      def change
        create_table charges_reservations do |t|
          t.integer :charge_id
          t.integer :reservation_id
        end
      end
    end
    

    run the migration from the command line:

    rails db:migrate
    

    Now make a join model:

    # app/models/charges_reservation.rb
    class ChargesReservation < ApplicationRecord
      belongs_to :charge
      belongs_to :reservation
    end
    

    Now you have to migrate the existing data, so from the rails console:

    Charge.all.each{|c| ChargesReservation.create(charge_id: c.id, reservation_id:c.reservation_id)}
    

    And finally change the associations to habtm associations as you have indicated in your question

    # charge.rb
    has_and_belongs_to_many :reservations
    
    #reservation.rb
    has_and_belongs_to_many :charges
    

    Oh and you can delete the reservation_id column in the charges table with another migration, once you are sure everything is working correctly. This is the point where you could create a problem b/c you're destroying data, so be sure that the join table was correctly populated.

    You actually don't need the join model any longer either, it was just a convenient way to populate the join table. So you can delete the charges_reservation.rb model.