How do you set HABTM values during migration?

Hi All,

I'm a bit perplexed as to how to set up certain values during
migration. My issue is similar to the following post:

http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/95e65b9f701a8f95/8bce8ff26f996da6?lnk=gst&q=admin+migration#8bce8ff26f996da6

...however, I have a setup with Users and Roles, and I need to assign
a role to an administrative user during migration. My setup uses
HABTM via join tables. Any ideas?

Thanks in advance.

Michael Williams wrote:

Hi All,

I'm a bit perplexed as to how to set up certain values during
migration. My issue is similar to the following post:

http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/95e65b9f701a8f95/8bce8ff26f996da6?lnk=gst&q=admin+migration#8bce8ff26f996da6

...however, I have a setup with Users and Roles, and I need to assign
a role to an administrative user during migration. My setup uses
HABTM via join tables. Any ideas?

Thanks in advance.

I have Users, Roles, and Permissions.

migration 001

def self.up
  # create your tables
  create_table :users
    # user fields
  end
  create_table :roles
    # role fields
  end
  create_table :permissions
    # permission fields
  end
  # then the data (I know that this is a new DB, and these are
  # the first two records created, so a 1, and 1 for record IDs works.
  User.create( set user fields)
  Role.create( set role fields)
  # I suppose I could do a find to get the IDs for User and
  # Role, but I don't need to
  Permission.create(:user_id => 1, :role_id => 1)
end

gberz3 wrote:

...however, I have a setup with Users and Roles, and I need to assign
a role to an administrative user during migration. My setup uses
HABTM via join tables. Any ideas?

Someone may suggest a specific technical fix.

My favorite general recommendation here is something called "deprecation refactor". That means you leave an old system online while developing a new one. Some deployments go out with both systems online.

So suppose we have Foo 1<-->* Bar, and we need Foo *<-->* Bar. Temporarily leave the foo_id in Bar, and add a bars_foos table. Get your tests and code passing with both has_many and habtm in the Foo table. (This might require some hacks that belie ActiveRecord's usual elegance, such as a Foo#get_bars method that conglomerates both its owned and linked Bars.) Any new records should start using the new system.

Wall-to-wall unit tests are critical here, because they allow your logic to temporarily survive a bad design.

Now deploy this intermediate design, and let it "soak" into the database.

After you have confidence the habtm itself works - such as after a week of working on other features within the new design - _now_ you write the migration that moves records out of the has_many association and into the habtm. The migration takes advantage of your experience with the new habtm working in practice.

Write a unit test for a migration like this (and any cleaner way would interest me!):

   def test_migration_convert_has_many_to_habtm
       # assemble the database up here
     require RailsRoot + 'db/migrate/042_has_many_to_habtm.rb'
     ConvertHasManyToHabtm.verbose = false
     ConvertHasManyToHabtm.up # activate the migration
       # assert here that foo_id variables are cleared, and
       # that bars_foos is populated
   end

Now deploy the migration, run the site for a while, and observe that all foo_id fields remain empty.

Finally, write another migration that retires the Bar.foo_id field. At this time, disable test_migration_convert_has_many_to_habtm (unless you want to start it by adding that field back!).

This post describes a very large, multi-phase "deprecation refactor". Only use this beast if your data records are very complex. (Some of ours are still in progress!:wink: For the simple matter of upgrading Foo.has_many :bars, you should still do all the steps in this order (per books like /Refactoring/), but you don't need to deploy and wait a while between each step.