mirror of
https://github.com/github/rails.git
synced 2026-01-29 00:08:15 -05:00
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]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@9122 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
*SVN*
|
||||
|
||||
* 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]
|
||||
|
||||
* Fixed that has_many :through would ignore the hash conditions #11447 [miloops]
|
||||
|
||||
* Fix issue where the :uniq option of a has_many :through association is ignored when find(:all) is called. Closes #9407 [cavalle]
|
||||
|
||||
@@ -431,7 +431,7 @@ module ActiveRecord #:nodoc:
|
||||
# adapters for, e.g., your development and test environments.
|
||||
cattr_accessor :schema_format , :instance_writer => false
|
||||
@@schema_format = :ruby
|
||||
|
||||
|
||||
class << self # Class methods
|
||||
# Find operates with four different retrieval approaches:
|
||||
#
|
||||
|
||||
@@ -232,12 +232,20 @@ module ActiveRecord
|
||||
|
||||
# Should not be called normally, but this operation is non-destructive.
|
||||
# The migrations module handles this automatically.
|
||||
def initialize_schema_information
|
||||
def initialize_schema_information(current_version=0)
|
||||
begin
|
||||
execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:integer)})"
|
||||
execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(0)"
|
||||
execute "CREATE TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version #{type_to_sql(:string)})"
|
||||
execute "INSERT INTO #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)} (version) VALUES(#{current_version})"
|
||||
rescue ActiveRecord::StatementInvalid
|
||||
# Schema has been initialized
|
||||
# Schema has been initialized, make sure version is a string
|
||||
version_column = columns(:schema_info).detect { |c| c.name == "version" }
|
||||
|
||||
# can't just alter the table, since SQLite can't deal
|
||||
unless version_column.type == :string
|
||||
version = ActiveRecord::Migrator.current_version
|
||||
execute "DROP TABLE #{quote_table_name(ActiveRecord::Migrator.schema_info_table_name)}"
|
||||
initialize_schema_information(version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -8,6 +8,12 @@ module ActiveRecord
|
||||
end
|
||||
end
|
||||
|
||||
class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
|
||||
def initialize(version)
|
||||
super("No migration with version number #{version}")
|
||||
end
|
||||
end
|
||||
|
||||
class IllegalMigrationNameError < ActiveRecordError#:nodoc:
|
||||
def initialize(name)
|
||||
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
||||
@@ -308,8 +314,6 @@ module ActiveRecord
|
||||
class Migrator#:nodoc:
|
||||
class << self
|
||||
def migrate(migrations_path, target_version = nil)
|
||||
Base.connection.initialize_schema_information
|
||||
|
||||
case
|
||||
when target_version.nil?, current_version < target_version
|
||||
up(migrations_path, target_version)
|
||||
@@ -319,6 +323,16 @@ module ActiveRecord
|
||||
return # You're on the right version
|
||||
end
|
||||
end
|
||||
|
||||
def rollback(migrations_path, steps=1)
|
||||
migrator = self.new(:down, migrations_path)
|
||||
start_index = migrator.migrations.index(migrator.current_migration)
|
||||
|
||||
return unless start_index
|
||||
|
||||
finish = migrator.migrations[start_index + steps]
|
||||
down(migrations_path, finish ? finish.version : 0)
|
||||
end
|
||||
|
||||
def up(migrations_path, target_version = nil)
|
||||
self.new(:up, migrations_path, target_version).migrate
|
||||
@@ -327,6 +341,10 @@ module ActiveRecord
|
||||
def down(migrations_path, target_version = nil)
|
||||
self.new(:down, migrations_path, target_version).migrate
|
||||
end
|
||||
|
||||
def run(direction, migrations_path, target_version)
|
||||
self.new(direction, migrations_path, target_version)
|
||||
end
|
||||
|
||||
def schema_info_table_name
|
||||
Base.table_name_prefix + "schema_info" + Base.table_name_suffix
|
||||
@@ -344,73 +362,90 @@ module ActiveRecord
|
||||
|
||||
def initialize(direction, migrations_path, target_version = nil)
|
||||
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
|
||||
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
||||
Base.connection.initialize_schema_information
|
||||
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
||||
end
|
||||
|
||||
def current_version
|
||||
self.class.current_version
|
||||
end
|
||||
|
||||
def current_migration
|
||||
migrations.detect { |m| m.version == current_version }
|
||||
end
|
||||
|
||||
def run
|
||||
target = migrations.detect { |m| m.version == @target_version }
|
||||
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
|
||||
target.migrate(@direction)
|
||||
end
|
||||
|
||||
def migrate
|
||||
migration_classes.each do |migration_class|
|
||||
if reached_target_version?(migration_class.version)
|
||||
Base.logger.info("Reached target version: #{@target_version}")
|
||||
break
|
||||
current = migrations.detect { |m| m.version == current_version }
|
||||
target = migrations.detect { |m| m.version == @target_version }
|
||||
|
||||
if target.nil? && !@target_version.nil? && @target_version > 0
|
||||
raise UnknownMigrationVersionError.new(@target_version)
|
||||
end
|
||||
|
||||
start = migrations.index(current) || 0
|
||||
finish = migrations.index(target) || migrations.size - 1
|
||||
runnable = migrations[start..finish]
|
||||
|
||||
# skip the current migration if we're heading upwards
|
||||
runnable.shift if up? && runnable.first == current
|
||||
|
||||
# skip the last migration if we're headed down, but not ALL the way down
|
||||
runnable.pop if down? && !target.nil?
|
||||
|
||||
runnable.each do |migration|
|
||||
Base.logger.info "Migrating to #{migration} (#{migration.version})"
|
||||
migration.migrate(@direction)
|
||||
set_schema_version_after_migrating(migration)
|
||||
end
|
||||
end
|
||||
|
||||
def migrations
|
||||
@migrations ||= begin
|
||||
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
|
||||
|
||||
migrations = files.inject([]) do |klasses, file|
|
||||
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
||||
|
||||
raise IllegalMigrationNameError.new(f) unless version
|
||||
version = version.to_i
|
||||
|
||||
if klasses.detect { |m| m.version == version }
|
||||
raise DuplicateMigrationVersionError.new(version)
|
||||
end
|
||||
|
||||
load(file)
|
||||
|
||||
klasses << returning(name.camelize.constantize) do |klass|
|
||||
class << klass; attr_accessor :version end
|
||||
klass.version = version
|
||||
end
|
||||
end
|
||||
|
||||
next if irrelevant_migration?(migration_class.version)
|
||||
|
||||
Base.logger.info "Migrating to #{migration_class} (#{migration_class.version})"
|
||||
migration_class.migrate(@direction)
|
||||
set_schema_version(migration_class.version)
|
||||
|
||||
migrations = migrations.sort_by(&:version)
|
||||
down? ? migrations.reverse : migrations
|
||||
end
|
||||
end
|
||||
|
||||
def pending_migrations
|
||||
migration_classes.select { |m| m.version > current_version }
|
||||
migrations.select { |m| m.version > current_version }
|
||||
end
|
||||
|
||||
private
|
||||
def migration_classes
|
||||
classes = migration_files.inject([]) do |migrations, migration_file|
|
||||
load(migration_file)
|
||||
version, name = migration_version_and_name(migration_file)
|
||||
assert_unique_migration_version(migrations, version.to_i)
|
||||
migrations << migration_class(name, version.to_i)
|
||||
end.sort_by(&:version)
|
||||
|
||||
down? ? classes.reverse : classes
|
||||
end
|
||||
|
||||
def assert_unique_migration_version(migrations, version)
|
||||
if !migrations.empty? && migrations.find { |m| m.version == version }
|
||||
raise DuplicateMigrationVersionError.new(version)
|
||||
def set_schema_version_after_migrating(migration)
|
||||
version = migration.version
|
||||
|
||||
if down?
|
||||
after = migrations[migrations.index(migration) + 1]
|
||||
version = after ? after.version : 0
|
||||
end
|
||||
end
|
||||
|
||||
def migration_files
|
||||
files = Dir["#{@migrations_path}/[0-9]*_*.rb"].sort_by do |f|
|
||||
m = migration_version_and_name(f)
|
||||
raise IllegalMigrationNameError.new(f) unless m
|
||||
m.first.to_i
|
||||
end
|
||||
down? ? files.reverse : files
|
||||
end
|
||||
|
||||
def migration_class(migration_name, version)
|
||||
klass = migration_name.camelize.constantize
|
||||
class << klass; attr_accessor :version end
|
||||
klass.version = version
|
||||
klass
|
||||
end
|
||||
|
||||
def migration_version_and_name(migration_file)
|
||||
return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
||||
end
|
||||
|
||||
def set_schema_version(version)
|
||||
Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
|
||||
|
||||
Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{version}")
|
||||
end
|
||||
|
||||
def up?
|
||||
@@ -420,14 +455,5 @@ module ActiveRecord
|
||||
def down?
|
||||
@direction == :down
|
||||
end
|
||||
|
||||
def reached_target_version?(version)
|
||||
return false if @target_version == nil
|
||||
(up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version)
|
||||
end
|
||||
|
||||
def irrelevant_migration?(version)
|
||||
(up? && version.to_i <= current_version) || (down? && version.to_i > current_version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -757,9 +757,9 @@ if ActiveRecord::Base.connection.supports_migrations?
|
||||
|
||||
def test_migrator_one_down
|
||||
ActiveRecord::Migrator.up(MIGRATIONS_ROOT + "/valid")
|
||||
|
||||
|
||||
ActiveRecord::Migrator.down(MIGRATIONS_ROOT + "/valid", 1)
|
||||
|
||||
|
||||
Person.reset_column_information
|
||||
assert Person.column_methods_hash.include?(:last_name)
|
||||
assert !Reminder.table_exists?
|
||||
@@ -805,6 +805,33 @@ if ActiveRecord::Base.connection.supports_migrations?
|
||||
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
|
||||
assert_equal "hello world", Reminder.find(:first).content
|
||||
end
|
||||
|
||||
def test_migrator_rollback
|
||||
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/valid")
|
||||
assert_equal(3, ActiveRecord::Migrator.current_version)
|
||||
|
||||
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
|
||||
assert_equal(2, ActiveRecord::Migrator.current_version)
|
||||
|
||||
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
|
||||
assert_equal(1, ActiveRecord::Migrator.current_version)
|
||||
|
||||
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
|
||||
ActiveRecord::Migrator.rollback(MIGRATIONS_ROOT + "/valid")
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
end
|
||||
|
||||
def test_migrator_run
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
ActiveRecord::Migrator.run(:up, MIGRATIONS_ROOT + "/valid", 3)
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
ActiveRecord::Migrator.run(:down, MIGRATIONS_ROOT + "/valid", 3)
|
||||
assert_equal(0, ActiveRecord::Migrator.current_version)
|
||||
end
|
||||
|
||||
def test_schema_info_table_name
|
||||
ActiveRecord::Base.table_name_prefix = "prefix_"
|
||||
@@ -892,15 +919,9 @@ if ActiveRecord::Base.connection.supports_migrations?
|
||||
end
|
||||
|
||||
def test_migrator_with_missing_version_numbers
|
||||
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
|
||||
assert !Person.column_methods_hash.include?(:middle_name)
|
||||
assert_equal 4, ActiveRecord::Migrator.current_version
|
||||
|
||||
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 2)
|
||||
Person.reset_column_information
|
||||
assert !Reminder.table_exists?
|
||||
assert Person.column_methods_hash.include?(:last_name)
|
||||
assert_equal 2, ActiveRecord::Migrator.current_version
|
||||
assert_raise(ActiveRecord::UnknownMigrationVersionError) do
|
||||
ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/missing", 500)
|
||||
end
|
||||
end
|
||||
|
||||
def test_create_table_with_custom_sequence_name
|
||||
|
||||
@@ -69,19 +69,8 @@ module Rails
|
||||
not existing_migrations(file_name).empty?
|
||||
end
|
||||
|
||||
def current_migration_number
|
||||
Dir.glob("#{RAILS_ROOT}/#{@migration_directory}/[0-9]*_*.rb").inject(0) do |max, file_path|
|
||||
n = File.basename(file_path).split('_', 2).first.to_i
|
||||
if n > max then n else max end
|
||||
end
|
||||
end
|
||||
|
||||
def next_migration_number
|
||||
current_migration_number + 1
|
||||
end
|
||||
|
||||
def next_migration_string(padding = 3)
|
||||
"%.#{padding}d" % next_migration_number
|
||||
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
||||
end
|
||||
|
||||
def gsub_file(relative_destination, regexp, *args, &block)
|
||||
|
||||
@@ -98,13 +98,26 @@ namespace :db do
|
||||
|
||||
desc 'Resets your database using your migrations for the current environment'
|
||||
task :reset => ["db:drop", "db:create", "db:migrate"]
|
||||
|
||||
desc 'Runs the "up" for a given migration VERSION.'
|
||||
task :up => :environment do
|
||||
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
||||
raise "VERSION is required" unless version
|
||||
ActiveRecord::Migrator.run(:up, "db/migrate/", version)
|
||||
end
|
||||
|
||||
desc 'Runs the "down" for a given migration VERSION.'
|
||||
task :down => :environment do
|
||||
version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
|
||||
raise "VERSION is required" unless version
|
||||
ActiveRecord::Migrator.run(:down, "db/migrate/", version)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Rolls the schema back to the previous version. Specify the number of steps with STEP=n'
|
||||
task :rollback => :environment do
|
||||
step = ENV['STEP'] ? ENV['STEP'].to_i : 1
|
||||
version = ActiveRecord::Migrator.current_version - step
|
||||
ActiveRecord::Migrator.migrate('db/migrate/', version)
|
||||
ActiveRecord::Migrator.rollback('db/migrate/', step)
|
||||
end
|
||||
|
||||
desc 'Drops and recreates the database from db/schema.rb for the current environment.'
|
||||
|
||||
Reference in New Issue
Block a user