migrext 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/features/import.feature +60 -0
- data/features/step_definitions/import.rb +85 -0
- data/features/support/base.rb +7 -0
- data/lib/migrext.rb +33 -0
- data/lib/migrext/importer.rb +25 -0
- data/lib/migrext/migration.rb +63 -0
- data/lib/migrext/migration_set.rb +28 -0
- data/lib/migrext/migrator.rb +14 -0
- data/lib/migrext/rails.rb +73 -0
- data/lib/tasks/migrext.rake +8 -0
- data/rails/init.rb +1 -0
- metadata +71 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
Feature: Import migrations
|
3
|
+
|
4
|
+
Scenario Outline: Support for Rails versions
|
5
|
+
Given a Rails <version> application
|
6
|
+
And a migration #1 in root
|
7
|
+
And a migration #2 in blue
|
8
|
+
And a migration #3 in red
|
9
|
+
When import all migrations
|
10
|
+
And migrate the database
|
11
|
+
Then migrations 1, 2 and 3 are loaded
|
12
|
+
|
13
|
+
Examples:
|
14
|
+
| version |
|
15
|
+
| 2 |
|
16
|
+
| 3 |
|
17
|
+
|
18
|
+
Scenario: migration versions have to be unique
|
19
|
+
If two extensions has migrations with the same number
|
20
|
+
the importer have to increment them to avoid collisions.
|
21
|
+
|
22
|
+
Given a Rails 3 application
|
23
|
+
And a migration #1 named "foo" in root
|
24
|
+
And a migration #1 named "bar" in blue
|
25
|
+
And a migration #1 named "baz" in red
|
26
|
+
When import all migrations
|
27
|
+
Then migration #1 is named "foo"
|
28
|
+
And migration #2 is named "blue_bar"
|
29
|
+
And migration #3 is named "red_baz"
|
30
|
+
|
31
|
+
Scenario: migration versions have to be greater than database version
|
32
|
+
If we try to import a migration with a number smaller than the
|
33
|
+
database version the importer has to increment migrations versions
|
34
|
+
|
35
|
+
Given a Rails 3 application
|
36
|
+
And a migration #30 named "one" in root
|
37
|
+
And a migration #100 named "two" in root
|
38
|
+
And a migration #15 named "three" in red
|
39
|
+
And a migration #50 named "four" in red
|
40
|
+
When migrate the database
|
41
|
+
And import all migrations
|
42
|
+
Then migration #30 is named "one"
|
43
|
+
And migration #100 is named "two"
|
44
|
+
And migration #101 is named "red_three"
|
45
|
+
And migration #102 is named "red_four"
|
46
|
+
|
47
|
+
Scenario: migration versions are kept if database version is smaller
|
48
|
+
The same migrations in the previous scenario, but now database version
|
49
|
+
is smaller than the imported migration versions
|
50
|
+
|
51
|
+
Given a Rails 3 application
|
52
|
+
And a migration #30 named "one" in root
|
53
|
+
And a migration #100 named "two" in root
|
54
|
+
And a migration #15 named "three" in red
|
55
|
+
And a migration #50 named "four" in red
|
56
|
+
When import all migrations
|
57
|
+
Then migration #15 is named "red_three"
|
58
|
+
And migration #30 is named "one"
|
59
|
+
And migration #50 is named "red_four"
|
60
|
+
And migration #100 is named "two"
|
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
Given /^a Rails (\S+) application$/ do |version|
|
3
|
+
|
4
|
+
@app_path = "/tmp/rails-migrext/test#$$#{rand(10000)}"
|
5
|
+
|
6
|
+
Process.wait(fork do
|
7
|
+
gem_version = "~> #{version}"
|
8
|
+
require 'rubygems'
|
9
|
+
gem 'rails', gem_version
|
10
|
+
|
11
|
+
ARGV.clear
|
12
|
+
case version
|
13
|
+
when /^2/
|
14
|
+
ARGV << @app_path
|
15
|
+
when /^3/
|
16
|
+
ARGV << "new" << @app_path
|
17
|
+
end
|
18
|
+
ARGV << "-d" << "sqlite3"
|
19
|
+
|
20
|
+
STDOUT.reopen "/dev/null", "w"
|
21
|
+
load Gem.bin_path('rails', 'rails', gem_version)
|
22
|
+
end)
|
23
|
+
|
24
|
+
# Load MigrExt
|
25
|
+
@lib_initializer = "#@app_path/config/initializers/migrext-sources.rb"
|
26
|
+
File.open(@lib_initializer, "a") {|f| f.puts "$: << #{File.expand_path("../../../lib/", __FILE__).inspect}\nrequire 'migrext/migrator'" }
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
Given /^a migration \#(\d+) in (\w+)$/ do |migration_number, source|
|
31
|
+
name = "ext_test_#{migration_number.scan(/./).map {|d| (d.to_i + 10).to_s(32) }.join }"
|
32
|
+
Given "a migration ##{migration_number} named \"#{name}\" in #{source}"
|
33
|
+
end
|
34
|
+
|
35
|
+
Given /^a migration \#(\d+) named "(\w+)" in (\w+)$/ do |migration_number, migration_name, source|
|
36
|
+
|
37
|
+
@migrations ||= []
|
38
|
+
|
39
|
+
if source == "root"
|
40
|
+
base_dir = "#@app_path/db/migrate"
|
41
|
+
else
|
42
|
+
base_dir = "#@app_path/exts-migrations-#{source}/"
|
43
|
+
end
|
44
|
+
|
45
|
+
if not File.directory?(base_dir)
|
46
|
+
Dir.mkdir base_dir
|
47
|
+
|
48
|
+
if source != "root"
|
49
|
+
File.open(@lib_initializer, "a") {|f| f.puts "ActiveRecord::Migrator.add_external_sources '#{source}', '#{base_dir}'" }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
migration_file = "#{base_dir}/#{migration_number}_#{migration_name}.rb"
|
54
|
+
File.exist?(migration_file).should be_false
|
55
|
+
|
56
|
+
File.open(migration_file, "w") do |f|
|
57
|
+
f.puts "class #{migration_name.camelize} < ActiveRecord::Migration\ndef self.down\nend\ndef self.up\nend\nend"
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
When /^import all migrations$/ do
|
63
|
+
MigrExt::RailsApplication.new(@app_path).import_externals!
|
64
|
+
end
|
65
|
+
|
66
|
+
When /^migrate the database$/ do
|
67
|
+
Process.wait(fork do
|
68
|
+
STDOUT.reopen "/dev/null", "w"
|
69
|
+
Dir.chdir @app_path
|
70
|
+
exec "rake", "db:migrate"
|
71
|
+
end)
|
72
|
+
end
|
73
|
+
|
74
|
+
Then /^migrations (.*) are loaded$/ do |migration_numbers|
|
75
|
+
migrations = MigrExt::RailsApplication.new(@app_path).current_migrations.map {|m| m.original_version }
|
76
|
+
migration_numbers.scan(/\d+/).each do |version|
|
77
|
+
migrations.should include(version.to_i)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
Then /^migration \#(\d+) is named "([^"]*)"$/ do |version, name|
|
82
|
+
versions = Dir["#@app_path/db/migrate/#{version}_*.rb"]
|
83
|
+
versions.length.should eql(1)
|
84
|
+
(versions.first =~ /\d+_(\w+)\.rb$/ && $1).should eql(name)
|
85
|
+
end
|
data/lib/migrext.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
|
2
|
+
require 'migrext/importer'
|
3
|
+
require 'migrext/rails'
|
4
|
+
require 'migrext/migration'
|
5
|
+
require 'migrext/migration_set'
|
6
|
+
|
7
|
+
if not "".respond_to?(:camelize)
|
8
|
+
class String
|
9
|
+
def camelize
|
10
|
+
gsub(/(?:^|_)(.)/) { $1.upcase }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if not "".respond_to?(:underscore)
|
16
|
+
class String
|
17
|
+
def underscore
|
18
|
+
gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
if defined? Rails::Railtie
|
24
|
+
class Migrext < Rails::Railtie
|
25
|
+
rake_tasks do
|
26
|
+
load "tasks/migrext.rake"
|
27
|
+
end
|
28
|
+
|
29
|
+
initializer :before, "migrext.loader" do
|
30
|
+
require "migrext/migrator"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module MigrExt
|
2
|
+
class Importer
|
3
|
+
attr_reader :application
|
4
|
+
|
5
|
+
def initialize(application)
|
6
|
+
@application = application
|
7
|
+
end
|
8
|
+
|
9
|
+
def run!
|
10
|
+
# Find available migrations
|
11
|
+
application.external_sources.map do |source|
|
12
|
+
set = MigrExt::MigrationSet.new(application, source[:name], source[:path])
|
13
|
+
|
14
|
+
set.current_migrations.each do |migration|
|
15
|
+
unless migration.imported?
|
16
|
+
migration.create_rooted_content!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#def
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module MigrExt
|
2
|
+
class Migration
|
3
|
+
attr_reader :set, :filename
|
4
|
+
|
5
|
+
attr_reader :version, :name, :plugin
|
6
|
+
attr_reader :original_version, :original_name, :original_plugin
|
7
|
+
|
8
|
+
def initialize(set, filename)
|
9
|
+
@set = set
|
10
|
+
@filename = filename
|
11
|
+
|
12
|
+
if filename =~ /(\d+)_(\w+)\.rb$/i
|
13
|
+
@original_version = @version = $1.to_i
|
14
|
+
@original_name = @name = $2
|
15
|
+
@original_plugin = @plugin = @set.name
|
16
|
+
end
|
17
|
+
|
18
|
+
if File.read(filename) =~ /#\s+imported\s+migration\s+(\d+)\s+(\w+)\s+from\s+(\w+)/i
|
19
|
+
@version = $1.to_i
|
20
|
+
@name = $2
|
21
|
+
@plugin = $3
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def ==(other_migration)
|
26
|
+
version == other_migration.version && name == other_migration.name && plugin == other_migration.plugin
|
27
|
+
end
|
28
|
+
|
29
|
+
def imported?
|
30
|
+
set.app.current_migrations.include?(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_rooted_content!
|
34
|
+
if set.root?
|
35
|
+
raise RuntimeError, "Set has to be from an extension root"
|
36
|
+
end
|
37
|
+
|
38
|
+
content = File.read(filename)
|
39
|
+
name = set.name + "_" + self.name
|
40
|
+
content.gsub! /^class \w+/, "class #{name.camelize}"
|
41
|
+
content << "\n\n# imported migration #@version #@name from #@plugin\n"
|
42
|
+
|
43
|
+
version = self.version.to_i
|
44
|
+
|
45
|
+
if version < set.app.database_version
|
46
|
+
version = set.app.database_version + 1
|
47
|
+
end
|
48
|
+
|
49
|
+
while true
|
50
|
+
break unless set.app.current_migrations.any? {|migration| migration.original_version == version }
|
51
|
+
version = version + 1
|
52
|
+
end
|
53
|
+
|
54
|
+
set.app.create_migration! version, name, content
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
"#<MigrExt::Migration #@version/#@name/#@plugin original[#@original_version/#@original_name/#@original_plugin] @set=#{@set.name.inspect}>"
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module MigrExt
|
2
|
+
class MigrationSet
|
3
|
+
attr_reader :app, :name, :path
|
4
|
+
|
5
|
+
def initialize(app, name, path)
|
6
|
+
@app = app
|
7
|
+
@name = name
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def root?
|
12
|
+
@name.nil?
|
13
|
+
end
|
14
|
+
|
15
|
+
def current_migrations
|
16
|
+
@current_migrations ||= begin
|
17
|
+
Dir["#@path/*"].map do |filename|
|
18
|
+
if filename =~ /\d+_\w+\.rb$/
|
19
|
+
MigrExt::Migration.new self, filename
|
20
|
+
else
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end.compact
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
if defined?(ActiveRecord::Migrator)
|
4
|
+
|
5
|
+
class ActiveRecord::Migrator
|
6
|
+
cattr_accessor :external_sources
|
7
|
+
self.external_sources = []
|
8
|
+
|
9
|
+
def self.add_external_sources(name, path)
|
10
|
+
external_sources << { :name => name, :path => path }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module MigrExt
|
5
|
+
class RailsApplication
|
6
|
+
|
7
|
+
def initialize(base_dir)
|
8
|
+
@base_dir = base_dir
|
9
|
+
end
|
10
|
+
|
11
|
+
def import_externals!
|
12
|
+
MigrExt::Importer.new(self).run!
|
13
|
+
end
|
14
|
+
|
15
|
+
def current_migrations
|
16
|
+
@current_migrations ||= MigrExt::MigrationSet.new(self, nil, "#@base_dir/db/migrate").current_migrations
|
17
|
+
end
|
18
|
+
|
19
|
+
def external_sources
|
20
|
+
@external_sources ||= begin
|
21
|
+
eval_in_application("ActiveRecord::Migrator.external_sources")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_migration!(version, name, content)
|
26
|
+
filename = "#@base_dir/db/migrate/#{version}_#{name.underscore}.rb"
|
27
|
+
raise RuntimeError, "Migration #{version}:#{name} exist" if File.exist?(filename)
|
28
|
+
|
29
|
+
File.open(filename, "w") {|f| f.write content }
|
30
|
+
@current_migrations = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def database_version
|
34
|
+
eval_in_application "ActiveRecord::Migrator.current_version"
|
35
|
+
end
|
36
|
+
|
37
|
+
def eval_in_application(code)
|
38
|
+
@drb_rails ||= begin
|
39
|
+
require 'drb'
|
40
|
+
rd, wr = IO.pipe
|
41
|
+
|
42
|
+
@rails_pid = fork do
|
43
|
+
rd.close
|
44
|
+
Dir.chdir @base_dir
|
45
|
+
|
46
|
+
def eval_code(code); eval code; end
|
47
|
+
|
48
|
+
require "#{@base_dir}/config/environment.rb"
|
49
|
+
|
50
|
+
DRb.start_service nil, self
|
51
|
+
wr.print DRb.uri
|
52
|
+
wr.close
|
53
|
+
|
54
|
+
DRb.thread.join
|
55
|
+
exit 1 # unreachable
|
56
|
+
end
|
57
|
+
wr.close
|
58
|
+
at_exit do
|
59
|
+
begin
|
60
|
+
Process.kill 15, @rails_pid
|
61
|
+
rescue Errno::ESRCH
|
62
|
+
# Process was died some time ago. Ignore the error.
|
63
|
+
true
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
DRbObject.new(nil, rd.read)
|
68
|
+
end
|
69
|
+
|
70
|
+
@drb_rails.eval_code code
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "migrext/migrator"
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: migrext
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
version: "0.1"
|
9
|
+
platform: ruby
|
10
|
+
authors:
|
11
|
+
- Ayose Cazorla
|
12
|
+
autorequire:
|
13
|
+
bindir: bin
|
14
|
+
cert_chain: []
|
15
|
+
|
16
|
+
date: 2010-08-15 00:00:00 +01:00
|
17
|
+
default_executable:
|
18
|
+
dependencies: []
|
19
|
+
|
20
|
+
description: Manage migrations from external sources
|
21
|
+
email: setepo@gmail.com
|
22
|
+
executables: []
|
23
|
+
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files: []
|
27
|
+
|
28
|
+
files:
|
29
|
+
- lib/migrext.rb
|
30
|
+
- lib/migrext/migrator.rb
|
31
|
+
- lib/migrext/migration_set.rb
|
32
|
+
- lib/migrext/migration.rb
|
33
|
+
- lib/migrext/importer.rb
|
34
|
+
- lib/migrext/rails.rb
|
35
|
+
- lib/tasks/migrext.rake
|
36
|
+
- rails/init.rb
|
37
|
+
- features/import.feature
|
38
|
+
- features/step_definitions/import.rb
|
39
|
+
- features/support/base.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage:
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options: []
|
46
|
+
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
segments:
|
54
|
+
- 0
|
55
|
+
version: "0"
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
requirements: []
|
64
|
+
|
65
|
+
rubyforge_project:
|
66
|
+
rubygems_version: 1.3.6
|
67
|
+
signing_key:
|
68
|
+
specification_version: 3
|
69
|
+
summary: Manage migrations from external sources
|
70
|
+
test_files: []
|
71
|
+
|