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

Ticket #11493: interleaved_migrations.diff

File interleaved_migrations.diff, 16.1 kB (added by jordi, 2 months ago)

Support for new migrations in the middle of the sequence

  • 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(current_version=0) 
    236         begin 
    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})" 
    239         rescue ActiveRecord::StatementInvalid 
    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) 
     235      def initialize_schema_migrations_table 
     236        sm_table = ActiveRecord::Migrator.schema_migrations_table_name 
     237 
     238        unless tables.detect { |t| t == sm_table } 
     239          create_table(sm_table, :id => false) do |schema_migrations_table| 
     240            schema_migrations_table.column :version, :string, :null => false 
    248241          end 
    249         end 
    250       end 
     242          add_index sm_table, :version, :unique => true, 
     243            :name => 'unique_schema_migrations' 
     244 
     245          # Backwards-compatibility: if we find schema_info, assume we've 
     246          # migrated up to that point: 
     247          si_table = Base.table_name_prefix + 'schema_info' + Base.table_name_suffix 
     248 
     249          if tables.detect { |t| t == si_table } 
    251250 
    252       def dump_schema_information #:nodoc: 
    253         begin 
    254           if (current_schema = ActiveRecord::Migrator.current_version) > 0 
    255             return "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES (#{current_schema})"  
     251            old_version = select_value("SELECT version FROM #{quote_table_name(si_table)}").to_i 
     252            assume_migrated_upto_version(old_version) 
     253            drop_table(si_table) 
    256254          end 
    257         rescue ActiveRecord::StatementInvalid  
    258           # No Schema Info 
    259255        end 
    260256      end 
    261257 
     258      def assume_migrated_upto_version(version) 
     259        sm_table = quote_table_name(ActiveRecord::Migrator.schema_migrations_table_name) 
     260        migrated = select_values("SELECT version FROM #{sm_table}").map(&:to_i) 
     261        versions = Dir['db/migrate/[0-9]*_*.rb'].map do |filename| 
     262          filename.split('/').last.split('_').first.to_i 
     263        end 
     264 
     265        execute "INSERT INTO #{sm_table} (version) VALUES ('#{version}')" 
     266        (versions - migrated).select { |v| v < version.to_i }.each do |v| 
     267          execute "INSERT INTO #{sm_table} (version) VALUES ('#{v}')" 
     268        end 
     269      end 
    262270 
    263271      def type_to_sql(type, limit = nil, precision = nil, scale = nil) #:nodoc: 
    264272        if native = native_database_types[type] 
  • a/activerecord/lib/active_record/migration.rb

    old new  
    123123  #  
    124124  # To run migrations against the currently configured database, use 
    125125  # <tt>rake db:migrate</tt>. This will update the database by running all of the 
    126   # pending migrations, creating the <tt>schema_info</tt> table if missing. 
     126  # pending migrations, creating the <tt>schema_migrations</tt> table if missing. 
    127127  # 
    128128  # To roll the database back to a previous migration version, use 
    129129  # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which 
     
    315315    class << self 
    316316      def migrate(migrations_path, target_version = nil) 
    317317        case 
    318           when target_version.nil?, current_version < target_version 
    319             up(migrations_path, target_version) 
    320           when current_version > target_version 
    321             down(migrations_path, target_version) 
    322           when current_version == target_version 
    323             return # You're on the right version 
     318          when target_version.nil?              then up(migrations_path, target_version) 
     319          when current_version > target_version then down(migrations_path, target_version) 
     320          else                                       up(migrations_path, target_version) 
    324321        end 
    325322      end 
    326        
     323 
    327324      def rollback(migrations_path, steps=1) 
    328325        migrator = self.new(:down, migrations_path) 
    329326        start_index = migrator.migrations.index(migrator.current_migration) 
     
    346343        self.new(direction, migrations_path, target_version).run 
    347344      end 
    348345 
    349       def schema_info_table_name 
    350         Base.table_name_prefix + "schema_info" + Base.table_name_suffix 
     346      def schema_migrations_table_name 
     347        Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix 
    351348      end 
    352349 
    353350      def current_version 
    354         Base.connection.select_value("SELECT version FROM #{schema_info_table_name}").to_i 
     351        Base.connection.select_values( 
     352          "SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).max || 0 
    355353      end 
    356354 
    357355      def proper_table_name(name) 
     
    362360 
    363361    def initialize(direction, migrations_path, target_version = nil) 
    364362      raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? 
    365       Base.connection.initialize_schema_information 
     363      Base.connection.initialize_schema_migrations_table 
    366364      @direction, @migrations_path, @target_version = direction, migrations_path, target_version       
    367365    end 
    368366 
     
    383381    def migrate 
    384382      current = migrations.detect { |m| m.version == current_version } 
    385383      target = migrations.detect { |m| m.version == @target_version } 
    386              
     384 
    387385      if target.nil? && !@target_version.nil? && @target_version > 0 
    388386        raise UnknownMigrationVersionError.new(@target_version) 
    389387      end 
    390388       
    391       start = migrations.index(current) || 0 
    392       finish = migrations.index(target) || migrations.size - 1       
     389      start = up? ? 0 : (migrations.index(current) || 0) 
     390      finish = migrations.index(target) || migrations.size - 1 
    393391      runnable = migrations[start..finish] 
    394392       
    395       # skip the current migration if we're heading upwards 
    396       runnable.shift if up? && runnable.first == current 
    397        
    398393      # skip the last migration if we're headed down, but not ALL the way down 
    399394      runnable.pop if down? && !target.nil? 
    400395       
    401396      runnable.each do |migration| 
    402397        Base.logger.info "Migrating to #{migration} (#{migration.version})" 
    403         migration.migrate(@direction) 
    404         set_schema_version_after_migrating(migration) 
     398 
     399        # On our way up, we skip migrating the ones we've already migrated 
     400        # On our way down, we skip reverting the ones we've never migrated 
     401        next if up? && migrated.include?(migration.version.to_i) 
     402 
     403        if down? && !migrated.include?(migration.version.to_i) 
     404          migration.announce 'never migrated, skipping'; migration.write 
     405        else 
     406          migration.migrate(@direction) 
     407          record_version_state_after_migrating(migration.version) 
     408        end 
    405409      end 
    406410    end 
    407411 
     
    412416        migrations = files.inject([]) do |klasses, file| 
    413417          version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first 
    414418           
    415           raise IllegalMigrationNameError.new(f) unless version 
     419          raise IllegalMigrationNameError.new(file) unless version 
    416420          version = version.to_i 
    417421           
    418422          if klasses.detect { |m| m.version == version } 
     
    432436      end 
    433437    end 
    434438 
    435     def pending_migrations 
    436       migrations.select { |m| m.version > current_version } 
     439    def migrated 
     440      sm_table = self.class.schema_migrations_table_name 
     441      Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort 
    437442    end 
    438443 
    439444    private 
    440       def set_schema_version_after_migrating(migration) 
    441         version = migration.version 
    442              
     445      def record_version_state_after_migrating(version) 
     446        sm_table = self.class.schema_migrations_table_name 
     447 
    443448        if down? 
    444           after = migrations[migrations.index(migration) + 1] 
    445           version = after ? after.version : 0 
     449          Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'") 
     450        else 
     451          Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')") 
    446452        end 
    447              
    448         Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}") 
    449453      end 
    450454 
    451455      def up? 
  • a/activerecord/lib/active_record/schema.rb

    old new  
    3434    # #add_index, etc.). 
    3535    # 
    3636    # The +info+ hash is optional, and if given is used to define metadata 
    37     # about the current schema (like the schema's version): 
     37    # about the current schema (currently, only the schema's version): 
    3838    # 
    39     #   ActiveRecord::Schema.define(:version => 15) do 
     39    #   ActiveRecord::Schema.define(:version => 20380119000001) do 
    4040    #     ... 
    4141    #   end 
    4242    def self.define(info={}, &block) 
    4343      instance_eval(&block) 
    4444 
    45       unless info.empty? 
    46         initialize_schema_information 
    47         cols = columns('schema_info') 
    48  
    49         info = info.map do |k,v| 
    50           v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s }) 
    51           "#{k} = #{v}" 
    52         end 
    53  
    54         Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}" 
     45      unless info[:version].blank? 
     46        initialize_schema_migrations_table 
     47        assume_migrated_upto_version info[:version] 
    5548      end 
    5649    end 
    5750  end 
  • a/activerecord/lib/active_record/schema_dumper.rb

    old new  
    3030      def initialize(connection) 
    3131        @connection = connection 
    3232        @types = @connection.native_database_types 
    33         @info = @connection.select_one("SELECT * FROM schema_info") rescue nil 
     33        @version = @connection.select_values( 
     34          "SELECT version FROM schema_migrations").map(&:to_i).max rescue nil 
    3435      end 
    3536 
    3637      def header(stream) 
    37         define_params = @info ? ":version => #{@info['version']}" : "" 
     38        define_params = @version ? ":version => #{@version}" : "" 
    3839 
    3940        stream.puts <<HEADER 
    4041# This file is auto-generated from the current state of the database. Instead of editing this file,  
     
    5960 
    6061      def tables(stream) 
    6162        @connection.tables.sort.each do |tbl| 
    62           next if ["schema_info", ignore_tables].flatten.any? do |ignored| 
     63          next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| 
    6364            case ignored 
    6465            when String; tbl == ignored 
    6566            when Regexp; tbl =~ ignored 
  • a/activerecord/test/cases/ar_schema_test.rb

    old new  
    2525      end 
    2626 
    2727      assert_nothing_raised { @connection.select_all "SELECT * FROM fruits" } 
    28       assert_nothing_raised { @connection.select_all "SELECT * FROM schema_info" } 
    29       assert_equal 7, @connection.select_one("SELECT version FROM schema_info")['version'].to_i 
     28      assert_nothing_raised { @connection.select_all "SELECT * FROM schema_migrations" } 
     29      assert_equal 7, @connection.select_values("SELECT version FROM schema_migrations").map(&:to_i).max 
    3030    end 
    3131  end 
    3232 
  • a/activerecord/test/cases/migration_test.rb

    old new  
    3434    end 
    3535 
    3636    def teardown 
    37       ActiveRecord::Base.connection.initialize_schema_information 
    38       ActiveRecord::Base.connection.update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0
     37      ActiveRecord::Base.connection.initialize_schema_migrations_table 
     38      ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}
    3939 
    4040      %w(reminders people_reminders prefix_reminders_suffix).each do |table| 
    4141        Reminder.connection.drop_table(table) rescue nil 
     
    833833      assert_equal(0, ActiveRecord::Migrator.current_version) 
    834834    end 
    835835 
    836     def test_schema_info_table_name 
     836    def test_schema_migrations_table_name 
    837837      ActiveRecord::Base.table_name_prefix = "prefix_" 
    838838      ActiveRecord::Base.table_name_suffix = "_suffix" 
    839839      Reminder.reset_table_name 
    840       assert_equal "prefix_schema_info_suffix", ActiveRecord::Migrator.schema_info_table_name 
     840      assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name 
    841841      ActiveRecord::Base.table_name_prefix = "" 
    842842      ActiveRecord::Base.table_name_suffix = "" 
    843843      Reminder.reset_table_name 
    844       assert_equal "schema_info", ActiveRecord::Migrator.schema_info_table_name 
     844      assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name 
    845845    ensure 
    846846      ActiveRecord::Base.table_name_prefix = "" 
    847847      ActiveRecord::Base.table_name_suffix = "" 
  • a/activerecord/test/cases/schema_dumper_test.rb

    old new  
    1616      output = standard_dump 
    1717      assert_match %r{create_table "accounts"}, output 
    1818      assert_match %r{create_table "authors"}, output 
    19       assert_no_match %r{create_table "schema_info"}, output 
     19      assert_no_match %r{create_table "schema_migrations"}, output 
    2020    end 
    2121 
    2222    def test_schema_dump_excludes_sqlite_sequence 
     
    8181      output = stream.string 
    8282      assert_no_match %r{create_table "accounts"}, output 
    8383      assert_match %r{create_table "authors"}, output 
    84       assert_no_match %r{create_table "schema_info"}, output 
     84      assert_no_match %r{create_table "schema_migrations"}, output 
    8585    end 
    8686 
    8787    def test_schema_dump_with_regexp_ignored_table 
     
    9292      output = stream.string 
    9393      assert_no_match %r{create_table "accounts"}, output 
    9494      assert_match %r{create_table "authors"}, output 
    95       assert_no_match %r{create_table "schema_info"}, output 
     95      assert_no_match %r{create_table "schema_migrations"}, output 
    9696    end 
    9797 
    9898    def test_schema_dump_illegal_ignored_table_value 
  • a/activerecord/test/schema/sybase.drop.sql

    old new  
    3131DROP TABLE numeric_data 
    3232DROP TABLE mixed_case_monkeys 
    3333DROP TABLE minimalistics 
    34 DROP TABLE schema_info 
     34DROP TABLE schema_migrations 
    3535go