Changeset 9122
- Timestamp:
- 03/28/08 21:21:01 (2 months ago)
- Files:
-
- trunk/activerecord/CHANGELOG (modified) (1 diff)
- trunk/activerecord/lib/active_record/base.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb (modified) (1 diff)
- trunk/activerecord/lib/active_record/migration.rb (modified) (7 diffs)
- trunk/activerecord/test/cases/migration_test.rb (modified) (3 diffs)
- trunk/railties/lib/rails_generator/commands.rb (modified) (1 diff)
- trunk/railties/lib/tasks/databases.rake (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/activerecord/CHANGELOG
r9110 r9122 1 1 *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] 2 4 3 5 * Fixed that has_many :through would ignore the hash conditions #11447 [miloops] trunk/activerecord/lib/active_record/base.rb
r9092 r9122 432 432 cattr_accessor :schema_format , :instance_writer => false 433 433 @@schema_format = :ruby 434 434 435 435 class << self # Class methods 436 436 # Find operates with four different retrieval approaches: trunk/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
r9056 r9122 233 233 # Should not be called normally, but this operation is non-destructive. 234 234 # The migrations module handles this automatically. 235 def initialize_schema_information 235 def initialize_schema_information(current_version=0) 236 236 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})" 239 239 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 241 249 end 242 250 end trunk/activerecord/lib/active_record/migration.rb
r9093 r9122 6 6 def initialize(version) 7 7 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}") 8 14 end 9 15 end … … 309 315 class << self 310 316 def migrate(migrations_path, target_version = nil) 311 Base.connection.initialize_schema_information312 313 317 case 314 318 when target_version.nil?, current_version < target_version … … 320 324 end 321 325 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 322 336 323 337 def up(migrations_path, target_version = nil) … … 327 341 def down(migrations_path, target_version = nil) 328 342 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) 329 347 end 330 348 … … 345 363 def initialize(direction, migrations_path, target_version = nil) 346 364 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_version348 365 Base.connection.initialize_schema_information 366 @direction, @migrations_path, @target_version = direction, migrations_path, target_version 349 367 end 350 368 … … 352 370 self.class.current_version 353 371 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 354 382 355 383 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 367 432 end 368 433 end 369 434 370 435 def pending_migrations 371 migration _classes.select { |m| m.version > current_version }436 migrations.select { |m| m.version > current_version } 372 437 end 373 438 374 439 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}") 414 449 end 415 450 … … 421 456 @direction == :down 422 457 end 423 424 def reached_target_version?(version)425 return false if @target_version == nil426 (up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version)427 end428 429 def irrelevant_migration?(version)430 (up? && version.to_i <= current_version) || (down? && version.to_i > current_version)431 end432 458 end 433 459 end trunk/activerecord/test/cases/migration_test.rb
r9056 r9122 758 758 def test_migrator_one_down 759 759 ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid") 760 760 761 761 ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1) 762 762 763 763 Person.reset_column_information 764 764 assert Person.column_methods_hash.include?(:last_name) … … 805 805 assert Reminder.create("content" => "hello world", "remind_at" => Time.now) 806 806 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) 807 834 end 808 835 … … 893 920 894 921 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 904 925 end 905 926 trunk/railties/lib/rails_generator/commands.rb
r8772 r9122 70 70 end 71 71 72 def current_migration_number73 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_i75 if n > max then n else max end76 end77 end78 79 def next_migration_number80 current_migration_number + 181 end82 83 72 def next_migration_string(padding = 3) 84 "%.#{padding}d" % next_migration_number73 Time.now.utc.strftime("%Y%m%d%H%M%S") 85 74 end 86 75 trunk/railties/lib/tasks/databases.rake
r9004 r9122 99 99 desc 'Resets your database using your migrations for the current environment' 100 100 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 101 115 end 102 116 … … 104 118 task :rollback => :environment do 105 119 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) 108 121 end 109 122