migrext 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,7 @@
1
+
2
+ @migrext_lib = File.expand_path("../../../lib/", __FILE__)
3
+ if not $:.include?(@migrext_lib)
4
+ $: << @migrext_lib
5
+ end
6
+
7
+ require 'migrext'
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
@@ -0,0 +1,8 @@
1
+
2
+ namespace :migrext do
3
+ desc "Import all external migrations using MigrExt"
4
+ task :import do
5
+ require 'migrext'
6
+ MigrExt::RailsApplication.new(Rake.original_dir).import_externals!
7
+ end
8
+ 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
+