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

Ticket #11493: interleaved_migrations_with_tests_and_docs.2.diff

File interleaved_migrations_with_tests_and_docs.2.diff, 21.5 kB (added by jordi, 1 month ago)

Interleaved migrations patch bugfixes, tests and docs

  • 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}')" unless migrated.include?(version.to_i) 
     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 
     127  # (see "About the schema_migrations table" section below) if missing. 
    127128  # 
    128129  # To roll the database back to a previous migration version, use 
    129130  # <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which 
     
    216217  # 
    217218  # The phrase "Updating salaries..." would then be printed, along with the 
    218219  # benchmark for the block when the block completes. 
     220  # 
     221  # == About the schema_migrations table 
     222  # 
     223  # Rails versions 2.0 and prior used to create a table called 
     224  # <tt>schema_info</tt> when using migrations. This table contained the 
     225  # version of the schema as of the last applied migration. 
     226  # 
     227  # Starting with Rails 2.1, the <tt>schema_info</tt> table is 
     228  # (automatically) replaced by the <tt>schema_migrations</tt> table, which 
     229  # contains the version numbers of all the migrations applied. 
     230  # 
     231  # As a result, it is now possible to add migration files that are numbered 
     232  # lower than the current schema version: when migrating up, those 
     233  # never-applied "interleaved" migrations will be automatically applied, and 
     234  # when migrating down, never-applied "interleaved" migrations will be skipped. 
    219235  class Migration 
    220236    @@verbose = true 
    221237    cattr_accessor :verbose 
     
    315331    class << self 
    316332      def migrate(migrations_path, target_version = nil) 
    317333        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 
     334          when target_version.nil?              then up(migrations_path, target_version) 
     335          when current_version > target_version then down(migrations_path, target_version) 
     336          else                                       up(migrations_path, target_version) 
    324337        end 
    325338      end 
    326        
     339 
    327340      def rollback(migrations_path, steps=1) 
    328341        migrator = self.new(:down, migrations_path) 
    329342        start_index = migrator.migrations.index(migrator.current_migration) 
     
    346359        self.new(direction, migrations_path, target_version).run 
    347360      end 
    348361 
    349       def schema_info_table_name 
    350         Base.table_name_prefix + "schema_info" + Base.table_name_suffix 
     362      def schema_migrations_table_name 
     363        Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix 
    351364      end 
    352365 
    353366      def current_version 
    354         Base.connection.select_value("SELECT version FROM #{schema_info_table_name}").to_i 
     367        Base.connection.select_values( 
     368          "SELECT version FROM #{schema_migrations_table_name}").map(&:to_i).max || 0 
    355369      end 
    356370 
    357371      def proper_table_name(name) 
     
    362376 
    363377    def initialize(direction, migrations_path, target_version = nil) 
    364378      raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? 
    365       Base.connection.initialize_schema_information 
     379      Base.connection.initialize_schema_migrations_table 
    366380      @direction, @migrations_path, @target_version = direction, migrations_path, target_version       
    367381    end 
    368382 
     
    383397    def migrate 
    384398      current = migrations.detect { |m| m.version == current_version } 
    385399      target = migrations.detect { |m| m.version == @target_version } 
    386              
     400 
    387401      if target.nil? && !@target_version.nil? && @target_version > 0 
    388402        raise UnknownMigrationVersionError.new(@target_version) 
    389403      end 
    390404       
    391       start = migrations.index(current) || 0 
    392       finish = migrations.index(target) || migrations.size - 1       
     405      start = up? ? 0 : (migrations.index(current) || 0) 
     406      finish = migrations.index(target) || migrations.size - 1 
    393407      runnable = migrations[start..finish] 
    394408       
    395       # skip the current migration if we're heading upwards 
    396       runnable.shift if up? && runnable.first == current 
    397        
    398409      # skip the last migration if we're headed down, but not ALL the way down 
    399410      runnable.pop if down? && !target.nil? 
    400411       
    401412      runnable.each do |migration| 
    402413        Base.logger.info "Migrating to #{migration} (#{migration.version})" 
    403         migration.migrate(@direction) 
    404         set_schema_version_after_migrating(migration) 
     414 
     415        # On our way up, we skip migrating the ones we've already migrated 
     416        # On our way down, we skip reverting the ones we've never migrated 
     417        next if up? && migrated.include?(migration.version.to_i) 
     418 
     419        if down? && !migrated.include?(migration.version.to_i) 
     420          migration.announce 'never migrated, skipping'; migration.write 
     421        else 
     422          migration.migrate(@direction) 
     423          record_version_state_after_migrating(migration.version) 
     424        end 
    405425      end 
    406426    end 
    407427 
     
    412432        migrations = files.inject([]) do |klasses, file| 
    413433          version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first 
    414434           
    415           raise IllegalMigrationNameError.new(f) unless version 
     435          raise IllegalMigrationNameError.new(file) unless version 
    416436          version = version.to_i 
    417437           
    418438          if klasses.detect { |m| m.version == version } 
     
    432452      end 
    433453    end 
    434454 
    435     def pending_migrations 
    436       migrations.select { |m| m.version > current_version } 
     455    def migrated 
     456      sm_table = self.class.schema_migrations_table_name 
     457      Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort 
    437458    end 
    438459 
    439460    private 
    440       def set_schema_version_after_migrating(migration) 
    441         version = migration.version 
    442              
     461      def record_version_state_after_migrating(version) 
     462        sm_table = self.class.schema_migrations_table_name 
     463 
    443464        if down? 
    444           after = migrations[migrations.index(migration) + 1] 
    445           version = after ? after.version : 0 
     465          Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'") 
     466        else 
     467          Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')") 
    446468        end 
    447              
    448         Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}") 
    449469      end 
    450470 
    451471      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 = Migrator::current_version rescue nil 
    3434      end 
    3535 
    3636      def header(stream) 
    37         define_params = @info ? ":version => #{@info['version']}" : "" 
     37        define_params = @version ? ":version => #{@version}" : "" 
    3838 
    3939        stream.puts <<HEADER 
    4040# This file is auto-generated from the current state of the database. Instead of editing this file,  
     
    5959 
    6060      def tables(stream) 
    6161        @connection.tables.sort.each do |tbl| 
    62           next if ["schema_info", ignore_tables].flatten.any? do |ignored| 
     62          next if ['schema_migrations', ignore_tables].flatten.any? do |ignored| 
    6363            case ignored 
    6464            when String; tbl == ignored 
    6565            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, Migrator::current_version 
    3030    end 
    3131  end 
    3232 
  • a/activerecord/test/cases/migration_test.rb

    old new  
    77require MIGRATIONS_ROOT + "/valid/1_people_have_last_names" 
    88require MIGRATIONS_ROOT + "/valid/2_we_need_reminders" 
    99require MIGRATIONS_ROOT + "/decimal/1_give_me_big_numbers" 
     10require MIGRATIONS_ROOT + "/interleaved/pass_3/2_i_raise_on_down" 
    1011 
    1112if ActiveRecord::Base.connection.supports_migrations? 
    1213  class BigNumber < ActiveRecord::Base; end 
     
    3435    end 
    3536 
    3637    def teardown 
    37       ActiveRecord::Base.connection.initialize_schema_information 
    38       ActiveRecord::Base.connection.update "UPDATE #{ActiveRecord::Migrator.schema_info_table_name} SET version = 0
     38      ActiveRecord::Base.connection.initialize_schema_migrations_table 
     39      ActiveRecord::Base.connection.execute "DELETE FROM #{ActiveRecord::Migrator.schema_migrations_table_name}
    3940 
    4041      %w(reminders people_reminders prefix_reminders_suffix).each do |table| 
    4142        Reminder.connection.drop_table(table) rescue nil 
     
    779780      assert !Reminder.table_exists? 
    780781    end 
    781782 
     783    def test_migrator_interleaved_migrations 
     784      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_1") 
     785 
     786      assert_nothing_raised do 
     787        ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/interleaved/pass_2") 
     788      end 
     789 
     790      Person.reset_column_information 
     791      assert Person.column_methods_hash.include?(:last_name) 
     792 
     793      assert_nothing_raised do 
     794        ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/interleaved/pass_3") 
     795      end 
     796    end 
     797 
    782798    def test_migrator_verbosity 
    783799      ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid", 1) 
    784800      assert PeopleHaveLastNames.message_count > 0 
     
    839855      assert_equal(0, ActiveRecord::Migrator.current_version) 
    840856    end 
    841857 
    842     def test_schema_info_table_name 
     858    def test_schema_migrations_table_name 
    843859      ActiveRecord::Base.table_name_prefix = "prefix_" 
    844860      ActiveRecord::Base.table_name_suffix = "_suffix" 
    845861      Reminder.reset_table_name 
    846       assert_equal "prefix_schema_info_suffix", ActiveRecord::Migrator.schema_info_table_name 
     862      assert_equal "prefix_schema_migrations_suffix", ActiveRecord::Migrator.schema_migrations_table_name 
    847863      ActiveRecord::Base.table_name_prefix = "" 
    848864      ActiveRecord::Base.table_name_suffix = "" 
    849865      Reminder.reset_table_name 
    850       assert_equal "schema_info", ActiveRecord::Migrator.schema_info_table_name 
     866      assert_equal "schema_migrations", ActiveRecord::Migrator.schema_migrations_table_name 
    851867    ensure 
    852868      ActiveRecord::Base.table_name_prefix = "" 
    853869      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 
  • /dev/null

    old new  
     1class InnocentJointable < ActiveRecord::Migration 
     2  def self.up 
     3    create_table("people_reminders", :id => false) do |t| 
     4      t.column :reminder_id, :integer 
     5      t.column :person_id, :integer 
     6    end 
     7  end 
     8 
     9  def self.down 
     10    drop_table "people_reminders" 
     11  end 
     12end 
  • /dev/null

    old new  
     1class PeopleHaveLastNames < ActiveRecord::Migration 
     2  def self.up 
     3    add_column "people", "last_name", :string 
     4  end 
     5 
     6  def self.down 
     7    remove_column "people", "last_name" 
     8  end 
     9end 
  • /dev/null

    old new  
     1class InnocentJointable < ActiveRecord::Migration 
     2  def self.up 
     3    create_table("people_reminders", :id => false) do |t| 
     4      t.column :reminder_id, :integer 
     5      t.column :person_id, :integer 
     6    end 
     7  end 
     8 
     9  def self.down 
     10    drop_table "people_reminders" 
     11  end 
     12end 
  • /dev/null

    old new  
     1class PeopleHaveLastNames < ActiveRecord::Migration 
     2  def self.up 
     3    add_column "people", "last_name", :string 
     4  end 
     5 
     6  def self.down 
     7    remove_column "people", "last_name" 
     8  end 
     9end 
  • /dev/null

    old new  
     1class IRaiseOnDown < ActiveRecord::Migration 
     2  def self.up 
     3  end 
     4 
     5  def self.down 
     6    raise 
     7  end 
     8end 
  • /dev/null

    old new  
     1class InnocentJointable < ActiveRecord::Migration 
     2  def self.up 
     3    create_table("people_reminders", :id => false) do |t| 
     4      t.column :reminder_id, :integer 
     5      t.column :person_id, :integer 
     6    end 
     7  end 
     8 
     9  def self.down 
     10    drop_table "people_reminders" 
     11  end 
     12end 
  • 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