konyak
konyak

Reputation: 11706

Rails: Convert has_many to has_one association

Assuming I have an existing app with the following models:

class User < ActiveRecord::Base
  has_many :locations, :through => :location_users
end

class Location < ActiveRecord::Base
  has_many :users, :through => :location_users
end

How do I go about converting this has_many to a has_one association like below, as far as migrations, trimming records of people with multiple locations, and anything else I missed? Are there any shortcuts to do this conversion?

class User < ActiveRecord::Base
  belongs_to :location
end

class Location < ActiveRecord::Base
  has_many :users
end

EDIT: User belongs to one and only one location

Upvotes: 0

Views: 1205

Answers (2)

usha
usha

Reputation: 29349

No Shortcuts.

Write a migration to add location_id to users table

class AddLocationIdToUsers < ActiveRecord::Migration
  def change
    add_column :users, :location_id, :integer
  end
end

And you can write another migration to populate the location_id for the existing users. For eg, if you want to populate the first location_id for the user in the locations_users table

class PopulateLocationIdOnUser < ActiveRecord::Migration
  def up
    #executing direct query to speed up the operation
    execute("update users set location_id = (select location_id from locations_users where locations_users.user_id = users.id limit 1)")
  end

  def down
    execute("update users set location_id = null")
  end
end

And another migration to drop locations_users table

class DropLocationsUsersTable < ActiveRecord::Migration
  def up
    drop_table :locations_users
  end

  def down
    create_table :locations_users do |t|
     #columns
    end
  end
end

You can also have a single migration to do all the three steps too.

Upvotes: 1

meatherly
meatherly

Reputation: 1861

There isn't really an easy way around this. You're going to have to do a lot of manually work from my experience. This is how I went about it:

  1. Write a migration to add user_id to the locations table.

  2. Run the migration

  3. Add the has_one relation code. (like you have above)

    class User < ActiveRecord::Base
      has_one :location
    end
    
    class Location < ActiveRecord::Base
     belongs_to :user
    end
    
  4. Either write a migrations to convert all the existing data over. (e.g. location.user = location.users.first). But it might be better in this case to write a rake task because this will only happen once and it will need to relay on your has_many relationship code to exist. So your migration would be invalid once you remove the has_many code.

  5. Run your rake task

  6. Remove the has_many code and join table.

After doing all that it should all work. Others might have a better way but this is how I did it.

Upvotes: 1

Related Questions