bare_migration.rb
上传用户:netsea168
上传日期:2022-07-22
资源大小:4652k
文件大小:5k
源码类别:

Ajax

开发平台:

Others

  1. # bare_migration.rb
  2. # Scott Bronson
  3. # 26 Jan 2006
  4. #
  5. # This module provides bare ActiveRecord::Base objects for migration
  6. # scripts.
  7. #
  8. # Most migration scripts use model classes to move data.  This is
  9. # fine when the schema you're migrating from is compatible with your
  10. # current models.  It's disastrous when your models have changed
  11. # significantly.
  12. #
  13. # This class allows you to use bare ActiveRecord classes in your migrate
  14. # scripts instead of your models so there is no chance of compatibility
  15. # problems.
  16. #
  17. # TODO: Make associations work (has_a, etc.) without requiring legacy calls
  18. # TODO: If a class already exists when defining it then Ruby just adds to
  19. #       the existing class.  This is why migration objects need to be
  20. #       named "BareArticle12" instead of just "BareArticle".  This stinks
  21. #       and should be fixed.  It won't be an easy fix though.
  22. # TODO: I know that STI (inheritance) won't work, but it should be
  23. #       simple enough to change instantiate_without_callbacks to make
  24. #       it work.
  25. # TODO: Create a test framework.
  26. # To use:
  27. #
  28. # - Create bare inner classes instead of models in your migration script.
  29. #   For instance, this would declare a bare object that matches an Article
  30. #   model:
  31. #
  32. #   class Migration < ActiveRecord::Migration
  33. #     class BareArticle
  34. #       include BareMigration
  35. #     end
  36. #     ...
  37. #   end
  38. #
  39. #   Don't forget to nest the declaration of the bare model in order to ensure
  40. #   that the class name is unique. Another option is to declare it outside
  41. #   the scope of the migration with a number based on the migration
  42. #   number, say Bare3Article
  43. #
  44. #   Use set_table_name if the object doesn't calculate its table name
  45. #   properly.
  46. #
  47. # - Then, use the bare objects to migrate the data:
  48. #
  49. #   BareArticle.find(:all).each do |a|
  50. #     a.published = 1
  51. #   end
  52. #
  53. # - Now your Article module can change all it wants and your migration
  54. #   script will still work.
  55. module BareMigration
  56.   def self.append_features(base)
  57.     base.extend(ClassMethods)
  58.     # Set the table name by eradicating "Bare" from the calculated table name.
  59.     # You can still use set_table_name if this is wrong.
  60.     base.set_table_name(base.table_name.sub(/.*?bared*_?/, ''))
  61.     # Default to ignoring the inheritance column
  62.     base.inheritance_column = :ignore_inheritance_column
  63.   end
  64.   module ClassMethods
  65.     # The problem with STI is that it causes AR to instantiate and
  66.     # return classes DIFFERENT from the class that called find()
  67.     # (classes that come from your app/models dir, a no-no when
  68.     # migrating).  For now, this routine overrides that behavior
  69.     # so that you will ONLY get the same class as the one that called
  70.     # find.  (todo: make STI work again)
  71.     def instantiate_without_callbacks(record)
  72.       object = allocate
  73.       object.instance_variable_set("@attributes", record)
  74.       object
  75.     end
  76.     def find_and_update(find_type=:all, *rest, &update_block)
  77.       self.transaction do
  78.         self.find(find_type, *rest).each do |item|
  79.           update_block[item]
  80.           item.save!
  81.         end
  82.       end
  83.     end
  84.   end
  85. end
  86. class ActiveRecord::Migration
  87.   def self.opposite_of(method)
  88.     method = method.to_s
  89.     method.sub!(/^add_/, 'remove_') || method.sub!(/^remove_/, 'add_') ||
  90.       method.sub!(/^create/, 'drop') || method.sub!(/^drop/, 'create') ||
  91.         method.sub!(/^rename_column/, 'reverse_columns')
  92.   end
  93.   def self.modify_schema(method, *args)
  94.     case method.to_s
  95.     when 'create_table'
  96.       block = args.last.is_a?(Proc) ? args.pop : Proc.new {|t| nil}
  97.       create_table *args, &block
  98.     when 'remove_column'
  99.       remove_column args[0], args[1]
  100.     when 'reverse_columns'
  101.       (table, to_col, from_col) = args
  102.       rename_column table, from_col, to_col
  103.     else
  104.       send(method, *args)
  105.     end
  106.   end
  107.   def self.modify_tables_and_update(*colspecs, &block)
  108.     unless colspecs.first.is_a?(Array)
  109.       colspecs = [colspecs]
  110.     end
  111.     begin
  112.       updated_classes = []
  113.       colspecs.each do |spec|
  114.         if spec[1].is_a?(Class)
  115.           updated_classes << spec[1]
  116.           spec[1] = spec[1].table_name.to_sym
  117.         end
  118.       end
  119.       colspecs.each {|spec| modify_schema(*spec) }
  120.       updated_classes.uniq!
  121.       if updated_classes.size == 1 && block && block.arity == 1
  122.         say "About to call find_and_update"
  123.         updated_classes.first.find_and_update(:all, &block)
  124.       else
  125.         block.call if block
  126.       end
  127.     rescue Exception => e
  128.       colspecs.reverse.each do |(method, table, column, *rest)|
  129.         modify_schema(opposite_of(method), table, column, *rest) rescue nil
  130.       end
  131.       raise e
  132.     end
  133.   end
  134. end