Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Ticket #11458: timestamped-migrations-3.diff

File timestamped-migrations-3.diff, 15.4 kB (added by jbarnette, 2 months ago)

10% less suckage (patch -p1)

  • a/activerecord/lib/active_record/base.rb

    old new  
    431431    # adapters for, e.g., your development and test environments. 
    432432    cattr_accessor :schema_format , :instance_writer => false 
    433433    @@schema_format = :ruby 
    434  
     434     
    435435    class << self # Class methods 
    436436      # Find operates with four different retrieval approaches: 
    437437      # 
  • a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb

    old new  
    232232 
    233233      # Should not be called normally, but this operation is non-destructive. 
    234234      # The migrations module handles this automatically. 
    235       def initialize_schema_information 
     235      def initialize_schema_information(current_version=0) 
    236236        begin 
    237           execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:integer)})" 
    238           execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(0)" 
     237          execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:string)})" 
     238          execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(#{current_version})" 
    239239        rescue ActiveRecord::StatementInvalid 
    240           # Schema has been initialized 
     240          # Schema has been initialized, make sure version is a string 
     241          version_column = columns(:schema_info).detect { |c| c.name == "version" } 
     242           
     243          # can't just alter the table, since SQLite can't deal 
     244          unless version_column.type == :string 
     245            version = ActiveRecord::Migrator.current_version 
     246            execute "DROP TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)}" 
     247            initialize_schema_information(version) 
     248          end 
    241249        end 
    242250      end 
    243251 
  • a/activerecord/lib/active_record/migration.rb

    old new  
    88    end 
    99  end 
    1010 
     11  class UnknownMigrationVersionError < ActiveRecordError #:nodoc: 
     12    def initialize(version) 
     13      super("No migration with version number #{version}") 
     14    end 
     15  end 
     16 
    1117  class IllegalMigrationNameError < ActiveRecordError#:nodoc: 
    1218    def initialize(name) 
    1319      super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)") 
     
    308314  class Migrator#:nodoc: 
    309315    class << self 
    310316      def migrate(migrations_path, target_version = nil) 
    311         Base.connection.initialize_schema_information 
    312  
    313317        case 
    314318          when target_version.nil?, current_version < target_version 
    315319            up(migrations_path, target_version) 
     
    319323            return # You're on the right version 
    320324        end 
    321325      end 
     326       
     327      def rollback(migrations_path, steps=1) 
     328        migrator = self.new(:down, migrations_path) 
     329        start_index = migrator.migrations.index(migrator.current_migration) 
     330         
     331        return unless start_index 
     332         
     333        finish = migrator.migrations[start_index + steps] 
     334        down(migrations_path, finish ? finish.version : 0) 
     335      end 
    322336 
    323337      def up(migrations_path, target_version = nil) 
    324338        self.new(:up, migrations_path, target_version).migrate 
     
    327341      def down(migrations_path, target_version = nil) 
    328342        self.new(:down, migrations_path, target_version).migrate 
    329343      end 
     344       
     345      def run(direction, migrations_path, target_version) 
     346        self.new(direction, migrations_path, target_version).run 
     347      end 
    330348 
    331349      def schema_info_table_name 
    332350        Base.table_name_prefix + "schema_info" + Base.table_name_suffix 
     
    344362 
    345363    def initialize(direction, migrations_path, target_version = nil) 
    346364      raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? 
    347       @direction, @migrations_path, @target_version = direction, migrations_path, target_version 
    348365      Base.connection.initialize_schema_information 
     366      @direction, @migrations_path, @target_version = direction, migrations_path, target_version       
    349367    end 
    350368 
    351369    def current_version 
    352370      self.class.current_version 
    353371    end 
     372     
     373    def current_migration 
     374      migrations.detect { |m| m.version == current_version } 
     375    end 
     376     
     377    def run 
     378      target = migrations.detect { |m| m.version == @target_version } 
     379      raise UnknownMigrationVersionError.new(@target_version) if target.nil? 
     380      target.migrate(@direction) 
     381    end 
    354382 
    355383    def migrate 
    356       migration_classes.each do |migration_class| 
    357         if reached_target_version?(migration_class.version) 
    358           Base.logger.info("Reached target version: #{@target_version}") 
    359           break 
    360         end 
    361  
    362         next if irrelevant_migration?(migration_class.version) 
     384      current = migrations.detect { |m| m.version == current_version } 
     385      target = migrations.detect { |m| m.version == @target_version } 
     386             
     387      if target.nil? && !@target_version.nil? && @target_version > 0 
     388        raise UnknownMigrationVersionError.new(@target_version) 
     389      end 
     390       
     391      start = migrations.index(current) || 0 
     392      finish = migrations.index(target) || migrations.size - 1       
     393      runnable = migrations[start..finish] 
     394       
     395      # skip the current migration if we're heading upwards 
     396      runnable.shift if up? && runnable.first == current 
     397       
     398      # skip the last migration if we're headed down, but not ALL the way down 
     399      runnable.pop if down? && !target.nil? 
     400       
     401      runnable.each do |migration| 
     402        Base.logger.info "Migrating to #{migration} (#{migration.version})" 
     403        migration.migrate(@direction) 
     404        set_schema_version_after_migrating(migration) 
     405      end 
     406    end 
    363407 
    364         Base.logger.info "Migrating to #{migration_class} (#{migration_class.version})" 
    365         migration_class.migrate(@direction) 
    366         set_schema_version(migration_class.version) 
     408    def migrations 
     409      @migrations ||= begin 
     410        files = Dir["#{@migrations_path}/[0-9]*_*.rb"] 
     411         
     412        migrations = files.inject([]) do |klasses, file| 
     413          version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first 
     414           
     415          raise IllegalMigrationNameError.new(f) unless version 
     416          version = version.to_i 
     417           
     418          if klasses.detect { |m| m.version == version } 
     419            raise DuplicateMigrationVersionError.new(version)  
     420          end 
     421           
     422          load(file) 
     423           
     424          klasses << returning(name.camelize.constantize) do |klass| 
     425            class << klass; attr_accessor :version end 
     426            klass.version = version 
     427          end 
     428        end 
     429         
     430        migrations = migrations.sort_by(&:version) 
     431        down? ? migrations.reverse : migrations 
    367432      end 
    368433    end 
    369434 
    370435    def pending_migrations 
    371       migration_classes.select { |m| m.version > current_version } 
     436      migrations.select { |m| m.version > current_version } 
    372437    end 
    373438 
    374439    private 
    375       def migration_classes 
    376         classes = migration_files.inject([]) do |migrations, migration_file| 
    377           load(migration_file) 
    378           version, name = migration_version_and_name(migration_file) 
    379           assert_unique_migration_version(migrations, version.to_i) 
    380           migrations << migration_class(name, version.to_i) 
    381         end.sort_by(&:version) 
    382  
    383         down? ? classes.reverse : classes 
    384       end 
    385  
    386       def assert_unique_migration_version(migrations, version) 
    387         if !migrations.empty? && migrations.find { |m| m.version == version } 
    388           raise DuplicateMigrationVersionError.new(version) 
    389         end 
    390       end 
    391  
    392       def migration_files 
    393         files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f| 
    394           m = migration_version_and_name(f) 
    395           raise IllegalMigrationNameError.new(f) unless m 
    396           m.first.to_i 
     440      def set_schema_version_after_migrating(migration) 
     441        version = migration.version 
     442             
     443        if down? 
     444          after = migrations[migrations.index(migration) + 1] 
     445          version = after ? after.version : 0 
    397446        end 
    398         down? ? files.reverse : files 
    399       end 
    400  
    401       def migration_class(migration_name, version) 
    402         klass = migration_name.camelize.constantize 
    403         class << klass; attr_accessor :version end 
    404         klass.version = version 
    405         klass 
    406       end 
    407  
    408       def migration_version_and_name(migration_file) 
    409         return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first 
    410       end 
    411  
    412       def set_schema_version(version) 
    413         Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}") 
     447             
     448        Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}") 
    414449      end 
    415450 
    416451      def up? 
     
    420455      def down? 
    421456        @direction == :down 
    422457      end 
    423  
    424       def reached_target_version?(version) 
    425         return false if @target_version == nil 
    426         (up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version) 
    427       end 
    428  
    429       def irrelevant_migration?(version) 
    430         (up? && version.to_i <= current_version) || (down? && version.to_i > current_version) 
    431       end 
    432458  end 
    433459end 
  • a/activerecord/test/cases/migration_test.rb

    old new  
    757757 
    758758    def test_migrator_one_down 
    759759      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") 
    760  
     760     
    761761      ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1) 
    762  
     762     
    763763      Person.reset_column_information 
    764764      assert Person.column_methods_hash.include?(:last_name) 
    765765      assert !Reminder.table_exists? 
     
    805805      assert Reminder.create("content" => "hello world", "remind_at" => Time.now) 
    806806      assert_equal "hello world", Reminder.find(:first).content 
    807807    end 
     808     
     809    def test_migrator_rollback 
     810      ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid") 
     811      assert_equal(3, ActiveRecord::Migrator.current_version) 
     812       
     813      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 
     814      assert_equal(2, ActiveRecord::Migrator.current_version)             
     815       
     816      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 
     817      assert_equal(1, ActiveRecord::Migrator.current_version)             
     818       
     819      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 
     820      assert_equal(0, ActiveRecord::Migrator.current_version)             
     821       
     822      ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid") 
     823      assert_equal(0, ActiveRecord::Migrator.current_version)             
     824    end 
     825     
     826    def test_migrator_run 
     827      assert_equal(0, ActiveRecord::Migrator.current_version) 
     828      ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3) 
     829      assert_equal(0, ActiveRecord::Migrator.current_version) 
     830 
     831      assert_equal(0, ActiveRecord::Migrator.current_version) 
     832      ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3) 
     833      assert_equal(0, ActiveRecord::Migrator.current_version) 
     834    end 
    808835 
    809836    def test_schema_info_table_name 
    810837      ActiveRecord::Base.table_name_prefix = "prefix_" 
     
    892919    end 
    893920 
    894921    def test_migrator_with_missing_version_numbers 
    895       ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500) 
    896       assert !Person.column_methods_hash.include?(:middle_name) 
    897       assert_equal 4, ActiveRecord::Migrator.current_version 
    898  
    899       ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 2) 
    900       Person.reset_column_information 
    901       assert !Reminder.table_exists? 
    902       assert Person.column_methods_hash.include?(:last_name) 
    903       assert_equal 2, ActiveRecord::Migrator.current_version 
     922      assert_raise(ActiveRecord::UnknownMigrationVersionError) do 
     923        ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500) 
     924      end 
    904925    end 
    905926 
    906927    def test_create_table_with_custom_sequence_name 
  • a/railties/lib/rails_generator/commands.rb

    old new  
    6969            not existing_migrations(file_name).empty? 
    7070          end 
    7171 
    72           def current_migration_number 
    73             Dir.glob("#{RAILS_ROOT}/#{@migration_directory}/[0-9]*_*.rb").inject(0) do |max, file_path| 
    74               n = File.basename(file_path).split('_', 2).first.to_i 
    75               if n > max then n else max end 
    76             end 
    77           end 
    78  
    79           def next_migration_number 
    80             current_migration_number + 1 
    81           end 
    82  
    8372          def next_migration_string(padding = 3) 
    84             "%.#{padding}d" % next_migration_number 
     73            Time.now.utc.strftime("%Y%m%d%H%M%S") 
    8574          end 
    8675 
    8776          def gsub_file(relative_destination, regexp, *args, &block) 
  • a/railties/lib/tasks/databases.rake

    old new  
    9898 
    9999    desc 'Resets your database using your migrations for the current environment' 
    100100    task :reset => ["db:drop", "db:create", "db:migrate"] 
     101     
     102    desc 'Runs the "up" for a given migration VERSION.' 
     103    task :up => :environment do 
     104      version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil 
     105      raise "VERSION is required" unless version 
     106      ActiveRecord::Migrator.run(:up, "db/migrate/", version) 
     107      Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby 
     108    end 
     109 
     110    desc 'Runs the "down" for a given migration VERSION.' 
     111    task :down => :environment do 
     112      version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil 
     113      raise "VERSION is required" unless version 
     114      ActiveRecord::Migrator.run(:down, "db/migrate/", version) 
     115      Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby 
     116    end     
    101117  end 
    102118 
    103119  desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n' 
    104120  task :rollback => :environment do 
    105121    step = ENV['STEP'] ? ENV['STEP'].to_i : 1 
    106     version = ActiveRecord::Migrator.current_version - step 
    107     ActiveRecord::Migrator.migrate('db/migrate/', version) 
     122    ActiveRecord::Migrator.rollback('db/migrate/', step) 
     123    Rake::Task["db:schema:dump"].invoke if ActiveRecord::Base.schema_format == :ruby 
    108124  end 
    109125 
    110126  desc 'Drops and recreates the database from db/schema.rb for the current environment.'