bare_migration.rb
上传用户:netsea168
上传日期:2022-07-22
资源大小:4652k
文件大小:5k
- # bare_migration.rb
- # Scott Bronson
- # 26 Jan 2006
- #
- # This module provides bare ActiveRecord::Base objects for migration
- # scripts.
- #
- # Most migration scripts use model classes to move data. This is
- # fine when the schema you're migrating from is compatible with your
- # current models. It's disastrous when your models have changed
- # significantly.
- #
- # This class allows you to use bare ActiveRecord classes in your migrate
- # scripts instead of your models so there is no chance of compatibility
- # problems.
- #
- # TODO: Make associations work (has_a, etc.) without requiring legacy calls
- # TODO: If a class already exists when defining it then Ruby just adds to
- # the existing class. This is why migration objects need to be
- # named "BareArticle12" instead of just "BareArticle". This stinks
- # and should be fixed. It won't be an easy fix though.
- # TODO: I know that STI (inheritance) won't work, but it should be
- # simple enough to change instantiate_without_callbacks to make
- # it work.
- # TODO: Create a test framework.
- # To use:
- #
- # - Create bare inner classes instead of models in your migration script.
- # For instance, this would declare a bare object that matches an Article
- # model:
- #
- # class Migration < ActiveRecord::Migration
- # class BareArticle
- # include BareMigration
- # end
- # ...
- # end
- #
- # Don't forget to nest the declaration of the bare model in order to ensure
- # that the class name is unique. Another option is to declare it outside
- # the scope of the migration with a number based on the migration
- # number, say Bare3Article
- #
- # Use set_table_name if the object doesn't calculate its table name
- # properly.
- #
- # - Then, use the bare objects to migrate the data:
- #
- # BareArticle.find(:all).each do |a|
- # a.published = 1
- # end
- #
- # - Now your Article module can change all it wants and your migration
- # script will still work.
- module BareMigration
- def self.append_features(base)
- base.extend(ClassMethods)
- # Set the table name by eradicating "Bare" from the calculated table name.
- # You can still use set_table_name if this is wrong.
- base.set_table_name(base.table_name.sub(/.*?bared*_?/, ''))
- # Default to ignoring the inheritance column
- base.inheritance_column = :ignore_inheritance_column
- end
- module ClassMethods
- # The problem with STI is that it causes AR to instantiate and
- # return classes DIFFERENT from the class that called find()
- # (classes that come from your app/models dir, a no-no when
- # migrating). For now, this routine overrides that behavior
- # so that you will ONLY get the same class as the one that called
- # find. (todo: make STI work again)
- def instantiate_without_callbacks(record)
- object = allocate
- object.instance_variable_set("@attributes", record)
- object
- end
- def find_and_update(find_type=:all, *rest, &update_block)
- self.transaction do
- self.find(find_type, *rest).each do |item|
- update_block[item]
- item.save!
- end
- end
- end
- end
- end
- class ActiveRecord::Migration
- def self.opposite_of(method)
- method = method.to_s
- method.sub!(/^add_/, 'remove_') || method.sub!(/^remove_/, 'add_') ||
- method.sub!(/^create/, 'drop') || method.sub!(/^drop/, 'create') ||
- method.sub!(/^rename_column/, 'reverse_columns')
- end
- def self.modify_schema(method, *args)
- case method.to_s
- when 'create_table'
- block = args.last.is_a?(Proc) ? args.pop : Proc.new {|t| nil}
- create_table *args, &block
- when 'remove_column'
- remove_column args[0], args[1]
- when 'reverse_columns'
- (table, to_col, from_col) = args
- rename_column table, from_col, to_col
- else
- send(method, *args)
- end
- end
- def self.modify_tables_and_update(*colspecs, &block)
- unless colspecs.first.is_a?(Array)
- colspecs = [colspecs]
- end
- begin
- updated_classes = []
- colspecs.each do |spec|
- if spec[1].is_a?(Class)
- updated_classes << spec[1]
- spec[1] = spec[1].table_name.to_sym
- end
- end
- colspecs.each {|spec| modify_schema(*spec) }
- updated_classes.uniq!
- if updated_classes.size == 1 && block && block.arity == 1
- say "About to call find_and_update"
- updated_classes.first.find_and_update(:all, &block)
- else
- block.call if block
- end
- rescue Exception => e
- colspecs.reverse.each do |(method, table, column, *rest)|
- modify_schema(opposite_of(method), table, column, *rest) rescue nil
- end
- raise e
- end
- end
- end