aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG2
-rwxr-xr-xactiverecord/lib/active_record/base.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb16
-rw-r--r--activerecord/lib/active_record/migration.rb148
-rw-r--r--activerecord/test/cases/migration_test.rb43
-rw-r--r--railties/lib/rails_generator/commands.rb13
-rw-r--r--railties/lib/tasks/databases.rake17
7 files changed, 150 insertions, 91 deletions
diff --git a/activerecord/CHANGELOG b/activerecord/CHANGELOG
index e8916b8d76..eed88ce4ae 100644
--- a/activerecord/CHANGELOG
+++ b/activerecord/CHANGELOG
@@ -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]
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
index ff72656e85..af480a0797 100755
--- a/activerecord/lib/active_record/base.rb
+++ b/activerecord/lib/active_record/base.rb
@@ -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:
#
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index b1f1fd4d73..c8913d0157 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -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
diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb
index 9945f9cd75..58374a01ad 100644
--- a/activerecord/lib/active_record/migration.rb
+++ b/activerecord/lib/active_record/migration.rb
@@ -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
- end
-
- next if irrelevant_migration?(migration_class.version)
+ 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
- Base.logger.info "Migrating to #{migration_class} (#{migration_class.version})"
- migration_class.migrate(@direction)
- set_schema_version(migration_class.version)
+ 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
+
+ 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)
- 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
+ def set_schema_version_after_migrating(migration)
+ version = migration.version
+
+ if down?
+ after = migrations[migrations.index(migration) + 1]
+ version = after ? after.version : 0
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
diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb
index ebe563059d..75c939f24f 100644
--- a/activerecord/test/cases/migration_test.rb
+++ b/activerecord/test/cases/migration_test.rb
@@ -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
diff --git a/railties/lib/rails_generator/commands.rb b/railties/lib/rails_generator/commands.rb
index 2f65918796..f74ca7722b 100644
--- a/railties/lib/rails_generator/commands.rb
+++ b/railties/lib/rails_generator/commands.rb
@@ -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)
diff --git a/railties/lib/tasks/databases.rake b/railties/lib/tasks/databases.rake
index 73259ee34d..bd96b23351 100644
--- a/railties/lib/tasks/databases.rake
+++ b/railties/lib/tasks/databases.rake
@@ -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.'