A collection of computer systems and programming tips that you may find useful.
Brought to you by Craic Computing LLC, a bioinformatics consulting company.

Thursday, June 13, 2013

Migrating from Rails HABTM to has_many :through

I had a has_and_belongs_to_many (HABTM) association that I needed to convert to has_many through with an explicit model that contains additional columns.

 In my example a User knows about many Words and each Word is known by many Users.

You can find many pages on the web about the differences. The consensus is that has_many :through is the way to go from the start - and after this process I agree.

Making the change in itself is not a big deal - drop the existing joining table 'users_words', create the new one, update the User and Word models and you're good to go.

Problem is that I already had data in the joining table...

And because the standard way of setting up a HABTM joining table does not include an id column, you can't just use that table or directly copy each record from it. Dang it...

Here were my steps - hopefully I got them all - don't skip any !

1: Backup your database and prevent users from accessing it

2: Do not touch any of the old associations, table, etc

3: Create the new table and model with a different name from the old joining table.
My HABTM table was users_words and my new table is user_work_links

4: Update the two models
My original association was this - do not change it yet !
  has_and_belongs_to_many :words, :uniq => true

The new association is this - NOTE the second line is commented out for now - VERY important !
  has_many :user_word_links, :dependent => :destroy
  # has_many :words, :through => :user_word_links, :uniq => true

5: Copy over the data from the old joining table with a rake task
You need to go through the existing associations one by one to get the ids for records in the two tables.
Here is my script:
namespace :craic do
  desc "move user word data"
  task :move_user_word_data => :environment  do
    users = User.all
    users.each do |user|
      user.words.each do |word|
        record = UserWordLink.new({ :user_id => user.id, :word_id => word.id })

6: Update the two models
Now you can comment out the old associations and uncomment the new ones
  # has_and_belongs_to_many :words, :uniq => true
  has_many :user_word_links, :dependent => :destroy
  has_many :words, :through => :user_word_links, :uniq => true

In the attr_accessible lists in the two models be sure to add :user_ids in the Word model and :word_ids in the User model. If your forget this it will silently fail to create the records

7: Test Test Test
You should be back up and running with the new model

8: Remove the old table
Finally create a migration that drops the old table and run it

Not too bad as long as you think it through before you start and don't rush the steps


Unknown said...

Very significant article for us , I think the representation of this article is actually superbly one. This is my first visit to your site.
Advertising agencies in Karachi

Unknown said...
This comment has been removed by the author.
humayoun said...

Nice article
Read my blog top marketing companies in Pakistan
real estate marketing companies in Islamabad
marketing companies in Islamabad

Archive of Tips