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

Changeset 9122

Show
Ignore:
Timestamp:
03/28/08 21:21:01 (2 months ago)
Author:
david
Message:

Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches. Also added rake db:migrate:up/down to apply individual migrations that may need to be run when you merge branches (closes #11458) [jbarnette]

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/activerecord/CHANGELOG

    r9110 r9122  
    11*SVN* 
     2 
     3* Switched to UTC-timebased version numbers for migrations and the schema. This will as good as eliminate the problem of multiple migrations getting the same version assigned in different branches. Also added rake db:migrate:up/down to apply individual migrations that may need to be run when you merge branches #11458 [jbarnette] 
    24 
    35* Fixed that has_many :through would ignore the hash conditions #11447 [miloops] 
  • trunk/activerecord/lib/active_record/base.rb

    r9092 r9122  
    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: 
  • trunk/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb

    r9056 r9122  
    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 
  • trunk/activerecord/lib/active_record/migration.rb

    r9093 r9122  
    66    def initialize(version) 
    77      super("Multiple migrations have the version number #{version}") 
     8    end 
     9  end 
     10 
     11  class UnknownMigrationVersionError < ActiveRecordError #:nodoc: 
     12    def initialize(version) 
     13      super("No migration with version number #{version}") 
    814    end 
    915  end 
     
    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 
     
    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) 
     
    327341      def down(migrations_path, target_version = nil) 
    328342        self.new(:down, migrations_path, target_version).migrate 
     343      end 
     344       
     345      def run(direction, migrations_path, target_version) 
     346        self.new(direction, migrations_path, target_version) 
    329347      end 
    330348 
     
    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 
     
    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) 
    363  
    364         Base.logger.info "Migrating to #{migration_class} (#{migration_class.version})" 
    365         migration_class.migrate(@direction) 
    366         set_schema_version(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 
     407 
     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 
    397         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}") 
     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 
     446        end 
     447             
     448        Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}") 
    414449      end 
    415450 
     
    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 
  • trunk/activerecord/test/cases/migration_test.rb

    r9056 r9122  
    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) 
     
    805805      assert Reminder.create("content" => "hello world", "remind_at" => Time.now) 
    806806      assert_equal "hello world", Reminder.find(:first).content 
     807    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) 
    807834    end 
    808835 
     
    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 
  • trunk/railties/lib/rails_generator/commands.rb

    r8772 r9122  
    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 
  • trunk/railties/lib/tasks/databases.rake

    r9004 r9122  
    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    end 
     108 
     109    desc 'Runs the "down" for a given migration VERSION.' 
     110    task :down => :environment do 
     111      version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil 
     112      raise "VERSION is required" unless version 
     113      ActiveRecord::Migrator.run(:down, "db/migrate/", version) 
     114    end     
    101115  end 
    102116 
     
    104118  task :rollback => :environment do 
    105119    step = ENV['STEP'] ? ENV['STEP'].to_i : 1 
    106     version = ActiveRecord::Migrator.current_version - step 
    107     ActiveRecord::Migrator.migrate('db/migrate/', version) 
     120    ActiveRecord::Migrator.rollback('db/migrate/', step) 
    108121  end 
    109122