Search code examples
ruby-on-railsruby-on-rails-4activerecordrails-migrations

How to migrate existing data after adding polymorphic association?


After migration my PageViewStats table contains 2 new empty columns: ad_templatable_id and ad_templatable_type. So if I try to query an existing record like so: AdTemplate.find(42).page_view_stats it returns nothing, but before migration it returns array of PageViewStats records.

I guess I need to create a rake task that will populate these 2 new columns with:

1) ad_templatable_id will contain values from existing ad_template_id column

2) ad_templatable_type will contain a string AdTemplate

My guess is correct? :) Is this what I need to do?

Before migration:

# == Schema Information
# #  ad_template_id      :integer          not null
class PageViewStat < ActiveRecord::Base
  belongs_to :ad_template
end

class AdTemplate < ActiveRecord::Base
  has_many :page_view_stats
end

Applying polymorpic migration:

class AddPolymorphismToPageViewStat < ActiveRecord::Migration
  def change
    add_reference :page_view_stats, :ad_templatable, polymorphic: true
    add_index :page_view_stats, [:ad_templatable_type, :ad_templatable_id],
               name: 'index_page_view_stats_on_ad_templatable_type_and_id'
  end
end

After migration:

# == Schema Information
# #  ad_template_id      :integer          not null
class PageViewStat < ActiveRecord::Base
  belongs_to :ad_templatable, polymorphic: true
end

class AdTemplate < ActiveRecord::Base
  has_many :page_view_stats, as: :ad_templatable
end

Solution

  • Yes, creating a rake task is better than performing data migration manually as you can write tests for it.

    Another way is to include code to change the data in the migration which is great to ensure that data is migrated when the schema changes, reducing potential downtime e.g.

    def up
      [code to add new columns]
    
      migrate_data
    
      [code to remove legacy columns]
    end
    
    def down
      [code to add legacy columns]
    
      rollback_data
    
      [code to remove new columns]
    end
    
    private
    
    def migrate_data
      [code to make data changes]
    end
    
    def rollback_data
      [code to reverse data changes]
    end
    

    Note, if you do it this way, there are some gotchas, you will need to specify up and down migration seperatly and you should go through the effort of writing code to rollback as well as test.