Nikhil
Nikhil

Reputation: 55

Update column with data in rails migration

I have an events table and sessions table. Events has_many sessions, this is the association. Now I want to move the time_zone column from the sessions table to events table only. So how do I do this with help of migrations. How do I move the existing records for time_zone in sessions table to events table?

Upvotes: 1

Views: 7357

Answers (2)

Greg Navis
Greg Navis

Reputation: 2934

First, you need to be sure that sessions associated with the same event have the same time zone. You can do this with:

Session.group(:event_id).count(:time_zone)

This will return a hash mapping an event_id to the number of time zones associated with it. This number should always be one.

Second, I recommend that you first add events.time_zone and start using it and remove sessions.time_zone in a separate migration after the new code has been in production for some time and is proved to work.

Third, the migration to add events.time_zone should look like this (I added some comments for clarity):

class AddTimeZoneToEvents < ActiveRecord::Migration
  class Event < ActiveRecord::Base; end
  class Session < ActiveRecord::Base; end

  def up
    # Add a NULLable time_zone column to events. Even if the column should be
    # non-NULLable, we first allow NULLs and will set the appropriate values
    # in the next step.
    add_column :events, :time_zone, :string

    # Ensure the new column is visible.
    Event.reset_column_information

    # Iterate over events in batches. Use #update_columns to set the newly
    # added time_zone without modifying updated_at. If you want to update
    # updated_at you have at least two options:
    #
    #   1. Set it to the time at which the migration is run. In this case, just
    #      replace #update_columns with #update!
    #   2. Set it to the maximum of `events.updated_at` and
    #      `sessions.updated_at`.
    #
    # Also, if your database is huge you may consider a different query to
    # perform the update (it also depends on your database).
    Event.find_each do |event|
      session = Session.where(event_id: event.id).last
      event.update_columns(time_zone: session.time_zone)
    end

    # If events don't always need to have time zone information then
    # you can remove the line below.
    change_column_null :events, :time_zone, false
  end

  def down
    remove_column :events, :time_zone
  end
end

Note that I redefined models in the migration. It's crucial to do so because:

  1. The original model may have callbacks and validations (sure, you can skip them but it's one extra precaution that contributes zero value).
  2. If you remove the models in 6 months down the road the migration will stop working.

Once you're sure your changes work as expected you can remove sessions.time_zone. If something goes awry you can simply roll back the above migration and restore a working version easily.

Upvotes: 4

Tushar Khatri
Tushar Khatri

Reputation: 19

You can simply use the following migration.

class Test < ActiveRecord::Migration
  def change
    add_column :events, :time_zone, :string

    Event.all.each do |e|
      e.update_attributes(time_zone: e.sessions.last.time_zone)
    end

    remove_column :sessions, :time_zone
  end
end

Upvotes: 0

Related Questions