Implemented ActiveRecord::Migrations#copy based on James Adam's idea
ActiveRecord::Migration#copy allows to copy migrations from one place to another, changing migrations versions and adding scope to filename. For example: ActiveRecord::Migration.copy("db/migrate", :blog_engine => "vendor/gems/blog/db/migrate") will copy all migrations from vendor/gems/blog/db/migrate to db/migrate with such format: Versions of copied migrations will be reversioned to be appended after migrations that already exists in db/migrate
This commit is contained in:
parent
2068b8cb6a
commit
75f8ac6ea7
@ -383,6 +383,37 @@ def method_missing(method, *arguments, &block)
|
||||
connection.send(method, *arguments, &block)
|
||||
end
|
||||
end
|
||||
|
||||
def copy(destination, sources)
|
||||
copied = []
|
||||
|
||||
sources.each do |scope, path|
|
||||
destination_migrations = ActiveRecord::Migrator.migrations(destination)
|
||||
source_migrations = ActiveRecord::Migrator.migrations(path)
|
||||
last = destination_migrations.last
|
||||
|
||||
source_migrations.each do |migration|
|
||||
next if destination_migrations.any? { |m| m.name == migration.name && m.scope == scope.to_s }
|
||||
|
||||
migration.version = next_migration_number(last.version + 1).to_i
|
||||
last = migration
|
||||
|
||||
new_path = File.join(destination, "#{migration.version}_#{migration.name.underscore}.#{scope}.rb")
|
||||
FileUtils.cp(migration.filename, new_path)
|
||||
copied << new_path
|
||||
end
|
||||
end
|
||||
|
||||
copied
|
||||
end
|
||||
|
||||
def next_migration_number(number)
|
||||
if ActiveRecord::Base.timestamped_migrations
|
||||
[Time.now.utc.strftime("%Y%m%d%H%M%S"), "%.14d" % number].max
|
||||
else
|
||||
"%.3d" % number
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -390,7 +421,7 @@ def method_missing(method, *arguments, &block)
|
||||
# until they are needed
|
||||
class MigrationProxy
|
||||
|
||||
attr_accessor :name, :version, :filename
|
||||
attr_accessor :name, :version, :filename, :scope
|
||||
|
||||
delegate :migrate, :announce, :write, :to=>:migration
|
||||
|
||||
@ -470,6 +501,34 @@ def migrations_path
|
||||
@migrations_path ||= 'db/migrate'
|
||||
end
|
||||
|
||||
def migrations(path)
|
||||
files = Dir["#{path}/[0-9]*_*.rb"]
|
||||
|
||||
migrations = files.inject([]) do |klasses, file|
|
||||
version, name, scope = file.scan(/([0-9]+)_([_a-z0-9]*)\.?([_a-z0-9]*)?.rb/).first
|
||||
|
||||
raise IllegalMigrationNameError.new(file) unless version
|
||||
version = version.to_i
|
||||
|
||||
if klasses.detect { |m| m.version == version }
|
||||
raise DuplicateMigrationVersionError.new(version)
|
||||
end
|
||||
|
||||
if klasses.detect { |m| m.name == name.camelize && m.scope == scope }
|
||||
raise DuplicateMigrationNameError.new(name.camelize)
|
||||
end
|
||||
|
||||
migration = MigrationProxy.new
|
||||
migration.name = name.camelize
|
||||
migration.version = version
|
||||
migration.filename = file
|
||||
migration.scope = scope
|
||||
klasses << migration
|
||||
end
|
||||
|
||||
migrations.sort_by(&:version)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def move(direction, migrations_path, steps)
|
||||
@ -548,30 +607,7 @@ def migrate
|
||||
|
||||
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(file) unless version
|
||||
version = version.to_i
|
||||
|
||||
if klasses.detect { |m| m.version == version }
|
||||
raise DuplicateMigrationVersionError.new(version)
|
||||
end
|
||||
|
||||
if klasses.detect { |m| m.name == name.camelize }
|
||||
raise DuplicateMigrationNameError.new(name.camelize)
|
||||
end
|
||||
|
||||
migration = MigrationProxy.new
|
||||
migration.name = name.camelize
|
||||
migration.version = version
|
||||
migration.filename = file
|
||||
klasses << migration
|
||||
end
|
||||
|
||||
migrations = migrations.sort_by { |m| m.version }
|
||||
migrations = self.class.migrations(@migrations_path)
|
||||
down? ? migrations.reverse : migrations
|
||||
end
|
||||
end
|
||||
|
@ -14,6 +14,12 @@ class Base < Rails::Generators::NamedBase #:nodoc:
|
||||
def self.base_root
|
||||
File.dirname(__FILE__)
|
||||
end
|
||||
|
||||
# Implement the required interface for Rails::Generators::Migration.
|
||||
def self.next_migration_number(dirname) #:nodoc:
|
||||
next_migration_number = current_migration_number(dirname) + 1
|
||||
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -83,3 +83,21 @@ def create_fixtures(*table_names, &block)
|
||||
ensure
|
||||
$stdout = original_stdout
|
||||
end
|
||||
|
||||
class << Time
|
||||
unless method_defined? :now_before_time_travel
|
||||
alias_method :now_before_time_travel, :now
|
||||
end
|
||||
|
||||
def now
|
||||
(@now ||= nil) || now_before_time_travel
|
||||
end
|
||||
|
||||
def travel_to(time, &block)
|
||||
@now = time
|
||||
block.call
|
||||
ensure
|
||||
@now = nil
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1875,5 +1875,116 @@ def with_change_table
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class CopyMigrationsTest < ActiveRecord::TestCase
|
||||
def setup
|
||||
end
|
||||
|
||||
def clear
|
||||
ActiveRecord::Base.timestamped_migrations = true
|
||||
to_delete = Dir[@migrations_path + "/*.rb"] - @existing_migrations
|
||||
File.delete(*to_delete)
|
||||
end
|
||||
|
||||
def test_copying_migrations_without_timestamps
|
||||
ActiveRecord::Base.timestamped_migrations = false
|
||||
@migrations_path = MIGRATIONS_ROOT + "/valid"
|
||||
@existing_migrations = Dir[@migrations_path + "/*.rb"]
|
||||
|
||||
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
|
||||
assert File.exists?(@migrations_path + "/4_people_have_hobbies.bukkits.rb")
|
||||
assert File.exists?(@migrations_path + "/5_people_have_descriptions.bukkits.rb")
|
||||
assert_equal [@migrations_path + "/4_people_have_hobbies.bukkits.rb", @migrations_path + "/5_people_have_descriptions.bukkits.rb"], copied
|
||||
|
||||
files_count = Dir[@migrations_path + "/*.rb"].length
|
||||
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy"})
|
||||
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
|
||||
assert copied.empty?
|
||||
ensure
|
||||
clear
|
||||
end
|
||||
|
||||
def test_copying_migrations_without_timestamps_from_2_sources
|
||||
ActiveRecord::Base.timestamped_migrations = false
|
||||
@migrations_path = MIGRATIONS_ROOT + "/valid"
|
||||
@existing_migrations = Dir[@migrations_path + "/*.rb"]
|
||||
|
||||
sources = ActiveSupport::OrderedHash.new
|
||||
sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy"
|
||||
ActiveRecord::Migration.copy(@migrations_path, sources)
|
||||
assert File.exists?(@migrations_path + "/4_people_have_hobbies.omg.rb")
|
||||
assert File.exists?(@migrations_path + "/5_people_have_descriptions.omg.rb")
|
||||
assert File.exists?(@migrations_path + "/6_people_have_hobbies.bukkits.rb")
|
||||
assert File.exists?(@migrations_path + "/7_people_have_descriptions.bukkits.rb")
|
||||
|
||||
files_count = Dir[@migrations_path + "/*.rb"].length
|
||||
ActiveRecord::Migration.copy(@migrations_path, sources)
|
||||
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
|
||||
ensure
|
||||
clear
|
||||
end
|
||||
|
||||
def test_copying_migrations_with_timestamps
|
||||
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
|
||||
@existing_migrations = Dir[@migrations_path + "/*.rb"]
|
||||
|
||||
Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do
|
||||
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
|
||||
assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb")
|
||||
assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb")
|
||||
expected = [@migrations_path + "/20100726101010_people_have_hobbies.bukkits.rb",
|
||||
@migrations_path + "/20100726101011_people_have_descriptions.bukkits.rb"]
|
||||
assert_equal expected, copied
|
||||
|
||||
files_count = Dir[@migrations_path + "/*.rb"].length
|
||||
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
|
||||
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
|
||||
assert copied.empty?
|
||||
end
|
||||
ensure
|
||||
clear
|
||||
end
|
||||
|
||||
def test_copying_migrations_with_timestamps_from_2_sources
|
||||
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
|
||||
@existing_migrations = Dir[@migrations_path + "/*.rb"]
|
||||
|
||||
sources = ActiveSupport::OrderedHash.new
|
||||
sources[:bukkits] = sources[:omg] = MIGRATIONS_ROOT + "/to_copy_with_timestamps"
|
||||
|
||||
Time.travel_to(created_at = Time.utc(2010, 7, 26, 10, 10, 10)) do
|
||||
copied = ActiveRecord::Migration.copy(@migrations_path, sources)
|
||||
assert File.exists?(@migrations_path + "/20100726101010_people_have_hobbies.omg.rb")
|
||||
assert File.exists?(@migrations_path + "/20100726101011_people_have_descriptions.omg.rb")
|
||||
assert File.exists?(@migrations_path + "/20100726101012_people_have_hobbies.bukkits.rb")
|
||||
assert File.exists?(@migrations_path + "/20100726101013_people_have_descriptions.bukkits.rb")
|
||||
assert_equal 4, copied.length
|
||||
|
||||
files_count = Dir[@migrations_path + "/*.rb"].length
|
||||
ActiveRecord::Migration.copy(@migrations_path, sources)
|
||||
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
|
||||
end
|
||||
ensure
|
||||
clear
|
||||
end
|
||||
|
||||
def test_copying_migrations_with_timestamps_to_destination_with_timestamps_in_future
|
||||
@migrations_path = MIGRATIONS_ROOT + "/valid_with_timestamps"
|
||||
@existing_migrations = Dir[@migrations_path + "/*.rb"]
|
||||
|
||||
Time.travel_to(created_at = Time.utc(2010, 2, 20, 10, 10, 10)) do
|
||||
ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
|
||||
assert File.exists?(@migrations_path + "/20100301010102_people_have_hobbies.bukkits.rb")
|
||||
assert File.exists?(@migrations_path + "/20100301010103_people_have_descriptions.bukkits.rb")
|
||||
|
||||
files_count = Dir[@migrations_path + "/*.rb"].length
|
||||
copied = ActiveRecord::Migration.copy(@migrations_path, {:bukkits => MIGRATIONS_ROOT + "/to_copy_with_timestamps"})
|
||||
assert_equal files_count, Dir[@migrations_path + "/*.rb"].length
|
||||
assert copied.empty?
|
||||
end
|
||||
ensure
|
||||
clear
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -0,0 +1,9 @@
|
||||
class PeopleHaveLastNames < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column "people", "hobbies", :text
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column "people", "hobbies"
|
||||
end
|
||||
end
|
@ -0,0 +1,9 @@
|
||||
class PeopleHaveLastNames < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column "people", "description", :text
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column "people", "description"
|
||||
end
|
||||
end
|
9
activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
Normal file
9
activerecord/test/migrations/to_copy_with_timestamps/20090101010101_people_have_hobbies.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class PeopleHaveLastNames < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column "people", "hobbies", :text
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column "people", "hobbies"
|
||||
end
|
||||
end
|
9
activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
Normal file
9
activerecord/test/migrations/to_copy_with_timestamps/20090101010202_people_have_descriptions.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class PeopleHaveLastNames < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column "people", "description", :text
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column "people", "description"
|
||||
end
|
||||
end
|
9
activerecord/test/migrations/valid_with_timestamps/20100101010101_people_have_last_names.rb
Normal file
9
activerecord/test/migrations/valid_with_timestamps/20100101010101_people_have_last_names.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class PeopleHaveLastNames < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column "people", "last_name", :string
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column "people", "last_name"
|
||||
end
|
||||
end
|
12
activerecord/test/migrations/valid_with_timestamps/20100201010101_we_need_reminders.rb
Normal file
12
activerecord/test/migrations/valid_with_timestamps/20100201010101_we_need_reminders.rb
Normal file
@ -0,0 +1,12 @@
|
||||
class WeNeedReminders < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table("reminders") do |t|
|
||||
t.column :content, :text
|
||||
t.column :remind_at, :datetime
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table "reminders"
|
||||
end
|
||||
end
|
12
activerecord/test/migrations/valid_with_timestamps/20100301010101_innocent_jointable.rb
Normal file
12
activerecord/test/migrations/valid_with_timestamps/20100301010101_innocent_jointable.rb
Normal file
@ -0,0 +1,12 @@
|
||||
class InnocentJointable < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table("people_reminders", :id => false) do |t|
|
||||
t.column :reminder_id, :integer
|
||||
t.column :person_id, :integer
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table "people_reminders"
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user