← Back to Upcase

Abstracting a model from an existing model with associations


(Matthew Sumner) #1

Hi all,

I have a application with a “calls” model that has many “trips”. Now I’ve shoehorned in a concept of recurring calls and trips into these models with a boolean flag. I want to correct this by abstracting out from these two models, two new models, “recurring_calls” and “recurring_trips”. Here is what I think I need to do:

  1. Create the “recurring_calls” and “recurring_trips” tables.
  2. Move and recurring calls and trips from the “calls” and “trips” tables to the new tables.
  3. Delete the recurring specific fields from the “calls” and “trips” tables.
  4. Move logic from call.rb and trip.rb into recurring_call.rb and recurring_trip.rb

My question is really concerning step 2. Is this something that can be achieved using ruby or do I need to write this in SQL? If I write this in SQL is it possible to preserve the associated records? i.e. if I move a call that has_many trips to a recurring_call can I keep that relation? I think I know how to achieve this in ruby however it seems the migration would then break if I dramatically change the call and trip models.

This is an app in production so preserving the data is top priority. However, these models have become too big and resistant to change. In the past I’ve left a lot of detail up to active record. if I do need to move these records using SQL how do I ensure the all the IDs are correctly assigned? Should I just copy these over seeing as they’re already unique?

Thanks.


(Matthew Sumner) #2

So here’s what I ended up doing:

    class RecurringCallsAndTrips < ActiveRecord::Migration
      def up
        create_table :recurring_calls do |t|
          ...
        end

        create_table :recurring_trips do |t|
          ...
        end

        add_indexes ...

        # move calls into recurring_calls
        columns = [ list of attributes ].join(", ")

        execute " INSERT INTO recurring_calls (#{columns})
                  SELECT #{columns} FROM calls
                  WHERE recurring = true"

        #move trips into recurring_trips
        attributes = [ list of attributes ]
        columns = attributes.join(", ")
        trip_columns = attributes.map {|attr| "trips.#{attr}"}.join(", ")

        execute " INSERT INTO recurring_trips (#{columns + ', recurring_call_id'})
                  SELECT #{trip_columns + ', trips.call_id'} FROM trips
                  INNER JOIN calls ON calls.id = trips.call_id
                  WHERE calls.recurring = true"

        # Delete the recurring calls and trips from calls and trips table
        execute " DELETE trips
                  FROM trips
                  INNER JOIN calls ON calls.id = trips.call_id
                  WHERE calls.recurring = true"
        execute " DELETE FROM calls WHERE recurring = true"

        # Remove the recurring specific fields
        remove_columns ...
      end

      def down
        add_columns ...

        columns = [ list of attributes ].join(", ")

        execute " INSERT INTO trips (#{columns + ', call_id'})
                  SELECT #{columns + ', recurring_call_id'} FROM recurring_trips"

        columns = [ list of attributes ].join(", ")

        execute " INSERT INTO calls (#{columns + ', recurring'})
                  SELECT #{columns + ', 1'} FROM recurring_calls"

        remove_indexs ...

        drop_table :recurring_trips
        drop_table :recurring_calls
      end
    end

Just for completeness.