enhanced_migrations 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Changelog +9 -0
- data/MIT-LICENSE +20 -0
- data/README +51 -0
- data/Rakefile +64 -0
- data/config/boot.rb +25 -0
- data/config/database.yml +11 -0
- data/config/environment.rb +10 -0
- data/config/environments/development.rb +21 -0
- data/config/environments/production.rb +18 -0
- data/config/environments/test.rb +19 -0
- data/config/routes.rb +22 -0
- data/db/migrate/1193798832_create_recipes_table.rb +12 -0
- data/db/migrate/1193800624_add_recipes_for_user1.rb +11 -0
- data/db/migrate/1193842239_add_recipes_for_user2.rb +11 -0
- data/lib/enhanced_migrations.rb +115 -0
- data/migrations/000_run_migrations_marker.rb +45 -0
- data/tasks/enhanced_migrations.rake +38 -0
- data/test/capture_stdout.rb +24 -0
- data/test/migration_test.rb +105 -0
- data/test/test_helper.rb +13 -0
- metadata +76 -0
data/Changelog
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
2007-11-05 Sean Soper <sean.soper@revolutionhealth.com>
|
2
|
+
* Ripping out of plugems architecture to make this into a standard plugin
|
3
|
+
|
4
|
+
2007-11-01 Sean Soper <sean.soper@revolutionhealth.com>
|
5
|
+
* Fixed bug where an empty migrations_info table would create a non-parseable schema.rb
|
6
|
+
* Made plugin database independent
|
7
|
+
* Added capability to step through migrations using VERSION=[previous, next, first and last]
|
8
|
+
* dump_schema_information now returns all migrations, not just latest (credit to François Beausolei at http://blog.teksol.info/2007/11/1/enhanced-migrations-plugin-enhancement)
|
9
|
+
* Added tests which use SQLite and a task (enhanced_migrations:clean_sqlite_db) to the harness's rakefile to help with testing
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Revolution Health Group LLC. All rights reserved.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
= Enhanced Rails Migrations
|
2
|
+
|
3
|
+
Enhanced Rails Migrations is a plugin that makes rails development easier in a large team with multiple code branches.
|
4
|
+
|
5
|
+
Native rails db migrations are great for most of the teams. The troubles begin when many developers try to add a migration or when code is merged between branches. Since native migrations are based on incremental numbers, the name clashing is frequent. Same happens with merged code on an even larger scale. This plugin was born at the Revolution Health Group team to address the problems with large multi-branched development (see our blog entry - http://revolutiononrails.blogspot.com/2007/01/db-migrations-usage-at-rhg.html) where it has been successfully used since February '07.
|
6
|
+
|
7
|
+
|
8
|
+
== The Solution
|
9
|
+
|
10
|
+
There are a couple of patches against the Edge rails approaching the problem from different angles. You might want to try them unless you work on 1.1.6 or prefer plugins to handle such things.
|
11
|
+
|
12
|
+
The plugin monkey-patches ActiveRecord classes to replace the sequential number based rails migration mechanism with the timestamp based one. That allows to avoid name collisions. In addition, it maintains a tracking table of already run migrations instead of the standard single number schema_info. The table is being used for decision whether to run a migration. The biggest difference from the standard way is that migrations below the current schema version are still applied if they have not been applied yet (not in the tracking table).
|
13
|
+
|
14
|
+
Important: See the First Run section for details how to use it in existing projects.
|
15
|
+
|
16
|
+
|
17
|
+
== Compatibility
|
18
|
+
|
19
|
+
The code has been used mostly on 1.1.6 and has been tested against 1.2.5.
|
20
|
+
|
21
|
+
|
22
|
+
== Setup
|
23
|
+
|
24
|
+
=== Installation
|
25
|
+
|
26
|
+
Install as any other Rails plugin:
|
27
|
+
|
28
|
+
ruby script/plugin install svn://rubyforge.org/var/svn/enhanced-mgrtns/enhanced_migrations
|
29
|
+
|
30
|
+
|
31
|
+
=== Usage
|
32
|
+
|
33
|
+
There is no difference in migration usage - use 'script/generate migration' to generate a new migration and 'rake db:migrate' for migration.
|
34
|
+
|
35
|
+
'schema_version' is depreciated. If you need to find the last run migration, use:
|
36
|
+
'SELECT MAX(id) FROM migrations_info'
|
37
|
+
|
38
|
+
|
39
|
+
=== First Run
|
40
|
+
|
41
|
+
If you are adding the plugin in a middle of the development process, when some migrations have been created and applied, you need to mark those as already run or 'rake db:migrate' would try to apply them again. The project contains a sample zero-number migration which queries the DB for the last run migration number and marks those in the new tracking table. It also deletes the schema_version table. To use it, copy 'migrations/000_run_migrations_marker.rb' from the project to your rails 'db/migrate' directory.
|
42
|
+
|
43
|
+
== License
|
44
|
+
|
45
|
+
Enhanced Rails Migrations is released under the MIT license.
|
46
|
+
|
47
|
+
|
48
|
+
== Support
|
49
|
+
|
50
|
+
The plugin RubyForge page is http://rubyforge.org/projects/enhanced-mgrtns
|
51
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/config/environment")
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/lib/enhanced_migrations")
|
3
|
+
|
4
|
+
require 'rake'
|
5
|
+
require 'rake/testtask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'tasks/rails'
|
9
|
+
|
10
|
+
Dir[File.dirname(__FILE__) + '/tasks/*.rake'].each { |rake| load rake }
|
11
|
+
|
12
|
+
# Custom rake task used to clean sqlite db files
|
13
|
+
SQLITE_DB_FILES = FileList['db/*.db']
|
14
|
+
|
15
|
+
namespace :enhanced_migrations do
|
16
|
+
task :clean_sqlite_db do
|
17
|
+
rm SQLITE_DB_FILES
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
desc 'Default: run unit tests.'
|
22
|
+
task :default => :test
|
23
|
+
|
24
|
+
desc 'Test the enhanced_migrations plugin.'
|
25
|
+
Rake::TestTask.new(:test) do |t|
|
26
|
+
t.libs << 'lib'
|
27
|
+
t.pattern = 'test/*_test.rb'
|
28
|
+
t.verbose = true
|
29
|
+
end
|
30
|
+
|
31
|
+
dist_dirs = [ "config", "db", "lib", "migrations", "tasks", "test" ]
|
32
|
+
|
33
|
+
gem_spec = Gem::Specification.new do |s|
|
34
|
+
s.platform = Gem::Platform::RUBY
|
35
|
+
s.name = "enhanced_migrations"
|
36
|
+
s.version = "1.2.0"
|
37
|
+
s.author = "RHG Team"
|
38
|
+
s.email = "rails-trunk@revolution.com"
|
39
|
+
s.summary = "Rails Enhanced Migrations"
|
40
|
+
s.files = [ "Changelog", "MIT-LICENSE", "Rakefile", "README" ]
|
41
|
+
dist_dirs.each do |dir|
|
42
|
+
s.files = s.files + Dir.glob( "#{dir}/**/*" ).delete_if { |item| /(\.[svn|log|db])|(schema\.rb)/ === item }
|
43
|
+
end
|
44
|
+
s.autorequire = 'enhanced_migrations'
|
45
|
+
s.require_path = "lib"
|
46
|
+
s.test_files = Dir.glob('test/*_test.rb')
|
47
|
+
s.has_rdoc = false
|
48
|
+
s.extra_rdoc_files = ["README", "Changelog"]
|
49
|
+
s.add_dependency('rails', '>= 1.1.6')
|
50
|
+
end
|
51
|
+
|
52
|
+
gem = Rake::GemPackageTask.new(gem_spec) do |pkg|
|
53
|
+
pkg.need_tar = true
|
54
|
+
pkg.need_zip = true
|
55
|
+
end
|
56
|
+
|
57
|
+
desc 'Generate documentation for the enhanced_migrations plugin.'
|
58
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
59
|
+
rdoc.rdoc_dir = 'rdoc'
|
60
|
+
rdoc.title = 'Enhanced Migrations'
|
61
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
62
|
+
rdoc.rdoc_files.include('README')
|
63
|
+
rdoc.rdoc_files.include('lib/*.rb')
|
64
|
+
end
|
data/config/boot.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
unless defined?(RAILS_ROOT)
|
2
|
+
root_path = File.join(File.dirname(__FILE__), '..')
|
3
|
+
|
4
|
+
unless RUBY_PLATFORM =~ /mswin32/
|
5
|
+
require 'pathname'
|
6
|
+
root_path = Pathname.new(root_path).cleanpath(true).to_s
|
7
|
+
end
|
8
|
+
|
9
|
+
RAILS_ROOT = root_path
|
10
|
+
end
|
11
|
+
|
12
|
+
unless defined?(Rails::Initializer)
|
13
|
+
if File.directory?("#{RAILS_ROOT}/vendor/rails")
|
14
|
+
require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
|
15
|
+
else
|
16
|
+
require 'rubygems'
|
17
|
+
|
18
|
+
rails_gem = Gem.cache.search('rails').find_all { |g| g.name == 'rails' }.sort { |a,b| a.version <=> b.version }.last rescue nil
|
19
|
+
raise "Couldn't find rails(#{rails_gem_version}" unless rails_gem
|
20
|
+
require_gem rails_gem.name, rails_gem.version.version
|
21
|
+
require rails_gem.full_gem_path + '/lib/initializer'
|
22
|
+
end
|
23
|
+
|
24
|
+
Rails::Initializer.run(:set_load_path)
|
25
|
+
end
|
data/config/database.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
unless defined? HAS_LOADED_ENVIRONMENT
|
2
|
+
HAS_LOADED_ENVIRONMENT = 1
|
3
|
+
require File.join(File.dirname(__FILE__), 'boot')
|
4
|
+
|
5
|
+
Rails::Initializer.run do |config|
|
6
|
+
config.frameworks -= [ :action_web_service, :action_mailer ]
|
7
|
+
end
|
8
|
+
|
9
|
+
RAILS_DEFAULT_LOGGER.info "Rails version: #{Rails::VERSION}"
|
10
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Settings specified here will take precedence over those in config/environment.rb
|
2
|
+
|
3
|
+
# In the development environment your application's code is reloaded on
|
4
|
+
# every request. This slows down response time but is perfect for development
|
5
|
+
# since you don't have to restart the webserver when you make code changes.
|
6
|
+
config.cache_classes = false
|
7
|
+
|
8
|
+
# Log error messages when you accidentally call methods on nil.
|
9
|
+
config.whiny_nils = true
|
10
|
+
|
11
|
+
# Enable the breakpoint server that script/breakpointer connects to
|
12
|
+
config.breakpoint_server = true
|
13
|
+
|
14
|
+
# Show full error reports and disable caching
|
15
|
+
config.action_controller.consider_all_requests_local = true
|
16
|
+
config.action_controller.perform_caching = false
|
17
|
+
config.action_view.cache_template_extensions = false
|
18
|
+
config.action_view.debug_rjs = true
|
19
|
+
|
20
|
+
# Don't care if the mailer can't send
|
21
|
+
config.action_mailer.raise_delivery_errors = false
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# Settings specified here will take precedence over those in config/environment.rb
|
2
|
+
|
3
|
+
# The production environment is meant for finished, "live" apps.
|
4
|
+
# Code is not reloaded between requests
|
5
|
+
config.cache_classes = true
|
6
|
+
|
7
|
+
# Use a different logger for distributed setups
|
8
|
+
# config.logger = SyslogLogger.new
|
9
|
+
|
10
|
+
# Full error reports are disabled and caching is turned on
|
11
|
+
config.action_controller.consider_all_requests_local = false
|
12
|
+
config.action_controller.perform_caching = true
|
13
|
+
|
14
|
+
# Enable serving of images, stylesheets, and javascripts from an asset server
|
15
|
+
# config.action_controller.asset_host = "http://assets.example.com"
|
16
|
+
|
17
|
+
# Disable delivery errors if you bad email addresses should just be ignored
|
18
|
+
# config.action_mailer.raise_delivery_errors = false
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Settings specified here will take precedence over those in config/environment.rb
|
2
|
+
|
3
|
+
# The test environment is used exclusively to run your application's
|
4
|
+
# test suite. You never need to work with it otherwise. Remember that
|
5
|
+
# your test database is "scratch space" for the test suite and is wiped
|
6
|
+
# and recreated between test runs. Don't rely on the data there!
|
7
|
+
config.cache_classes = true
|
8
|
+
|
9
|
+
# Log error messages when you accidentally call methods on nil.
|
10
|
+
config.whiny_nils = true
|
11
|
+
|
12
|
+
# Show full error reports and disable caching
|
13
|
+
config.action_controller.consider_all_requests_local = true
|
14
|
+
config.action_controller.perform_caching = false
|
15
|
+
|
16
|
+
# Tell ActionMailer not to deliver emails to the real world.
|
17
|
+
# The :test delivery method accumulates sent emails in the
|
18
|
+
# ActionMailer::Base.deliveries array.
|
19
|
+
config.action_mailer.delivery_method = :test
|
data/config/routes.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
ActionController::Routing::Routes.draw do |map|
|
2
|
+
# The priority is based upon order of creation: first created -> highest priority.
|
3
|
+
|
4
|
+
# Sample of regular route:
|
5
|
+
# map.connect 'products/:id', :controller => 'catalog', :action => 'view'
|
6
|
+
# Keep in mind you can assign values other than :controller and :action
|
7
|
+
|
8
|
+
# Sample of named route:
|
9
|
+
# map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase'
|
10
|
+
# This route can be invoked with purchase_url(:id => product.id)
|
11
|
+
|
12
|
+
# You can have the root of your site routed by hooking up ''
|
13
|
+
# -- just remember to delete public/index.html.
|
14
|
+
# map.connect '', :controller => "welcome"
|
15
|
+
|
16
|
+
# Allow downloading Web Service WSDL as a file with an extension
|
17
|
+
# instead of a file named 'wsdl'
|
18
|
+
map.connect ':controller/service.wsdl', :action => 'wsdl'
|
19
|
+
|
20
|
+
# Install the default route as the lowest priority.
|
21
|
+
map.connect ':controller/:action/:id'
|
22
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddRecipesForUser1 < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
execute "INSERT INTO recipes (name, owner) VALUES ('Lemon Meringue Pie', 'user1')"
|
4
|
+
execute "INSERT INTO recipes (name, owner) VALUES ('Blueberry Pie', 'user1')"
|
5
|
+
execute "INSERT INTO recipes (name, owner) VALUES ('Sugar Cream Pie', 'user1')"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.down
|
9
|
+
execute "DELETE FROM recipes WHERE owner = 'user1'"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AddRecipesForUser2 < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
execute "INSERT INTO recipes (name, owner) VALUES ('Steak and Kidney Pie', 'user2')"
|
4
|
+
execute "INSERT INTO recipes (name, owner) VALUES ('Shephards Pie', 'user2')"
|
5
|
+
execute "INSERT INTO recipes (name, owner) VALUES ('Pot Pie', 'user2')"
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.down
|
9
|
+
execute "DELETE FROM recipes WHERE owner = 'user2'"
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Migrator
|
3
|
+
|
4
|
+
def self.migrate(migrations_path, target_version = nil)
|
5
|
+
|
6
|
+
ActiveRecord::Base.connection.initialize_schema_information
|
7
|
+
|
8
|
+
if target_version.nil? || (current_version <= target_version)
|
9
|
+
up(migrations_path, target_version)
|
10
|
+
else
|
11
|
+
down(migrations_path, target_version)
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.current_version
|
17
|
+
ActiveRecord::Base.connection.select_one("SELECT MAX(id) as max FROM #{schema_info_table_name}")["max"].to_i rescue 0
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.schema_info_table_name
|
21
|
+
ActiveRecord::Base.table_name_prefix + "migrations_info" + ActiveRecord::Base.table_name_suffix
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def already_migrated?(version)
|
28
|
+
ActiveRecord::Base.connection.select_one("SELECT id FROM #{self.class.schema_info_table_name} WHERE id = #{version}") != nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def set_schema_version(version)
|
32
|
+
if down?
|
33
|
+
ActiveRecord::Base.connection.execute("DELETE FROM #{self.class.schema_info_table_name} WHERE id = #{version}")
|
34
|
+
else
|
35
|
+
ActiveRecord::Base.connection.execute("INSERT INTO #{self.class.schema_info_table_name} VALUES(#{version}, #{ActiveRecord::Base.connection.quote(Time.now)})")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def irrelevant_migration?(version)
|
40
|
+
(up? && already_migrated?(version)) || (down? && (not already_migrated?(version)))
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
### Fixing all places where schema_info was used
|
48
|
+
|
49
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements.send(:define_method, :initialize_schema_information) do
|
50
|
+
begin
|
51
|
+
ActiveRecord::Base.connection.execute "CREATE TABLE #{ActiveRecord::Migrator.schema_info_table_name} (id #{type_to_sql(:integer)}, created_at #{type_to_sql(:datetime)}, PRIMARY KEY (id))"
|
52
|
+
rescue ActiveRecord::StatementInvalid
|
53
|
+
# Migrations schema has been intialized
|
54
|
+
end
|
55
|
+
end
|
56
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements.send(:public, :initialize_schema_information)
|
57
|
+
|
58
|
+
|
59
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements.send(:define_method, :dump_schema_information) do
|
60
|
+
begin
|
61
|
+
select_all("SELECT * FROM #{ActiveRecord::Migrator.schema_info_table_name} ORDER BY created_at, id").map do |migration|
|
62
|
+
"INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} VALUES(#{migration["id"]}, '#{migration["created_at"]}');\n"
|
63
|
+
end
|
64
|
+
rescue ActiveRecord::StatementInvalid
|
65
|
+
# No Schema Info
|
66
|
+
end
|
67
|
+
end
|
68
|
+
ActiveRecord::ConnectionAdapters::SchemaStatements.send(:public, :dump_schema_information)
|
69
|
+
|
70
|
+
|
71
|
+
ActiveRecord::SchemaDumper.send(:define_method, :initialize) do |connection|
|
72
|
+
@connection = connection
|
73
|
+
@types = @connection.native_database_types
|
74
|
+
version_id = @connection.select_one("SELECT MAX(id) as max FROM migrations_info")["max"] rescue nil
|
75
|
+
@info = version_id ? {'version' => version_id} : nil
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
ActiveRecord::SchemaDumper.send(:define_method, :tables) do |stream|
|
80
|
+
@connection.tables.sort.each do |tbl|
|
81
|
+
next if ["migrations_info", ignore_tables].flatten.any? do |ignored|
|
82
|
+
case ignored
|
83
|
+
when String: tbl == ignored
|
84
|
+
when Regexp: tbl =~ ignored
|
85
|
+
else
|
86
|
+
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
table(tbl, stream)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# For enhanced migrations schema define info does not do much since previous migrations are applied regardless of the current version
|
94
|
+
module ActiveRecord
|
95
|
+
class Schema < Migration
|
96
|
+
|
97
|
+
def self.define(info={}, &block)
|
98
|
+
instance_eval(&block)
|
99
|
+
|
100
|
+
if info[:version]
|
101
|
+
initialize_schema_information
|
102
|
+
if Base.connection.select_one("SELECT id FROM #{ActiveRecord::Migrator.schema_info_table_name} WHERE id = #{info[:version]}") == nil
|
103
|
+
Base.connection.execute("INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} VALUES(#{info[:version]}, #{ActiveRecord::Base.connection.quote(Time.now)})")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
require 'rails_generator/base'
|
112
|
+
require 'rails_generator/commands'
|
113
|
+
Rails::Generator::Commands::Base.send(:define_method, :next_migration_number) do
|
114
|
+
Time.now.utc.to_i.to_s
|
115
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class RunMigrationsMarker < ActiveRecord::Migration
|
2
|
+
class << self
|
3
|
+
|
4
|
+
def up
|
5
|
+
|
6
|
+
begin
|
7
|
+
|
8
|
+
migration_numbers.each do |migration_number|
|
9
|
+
break if migration_number > old_migration_number
|
10
|
+
mark_already_run_migration(migration_number) if migration_number > 0
|
11
|
+
end
|
12
|
+
|
13
|
+
drop_table old_schema_info_table_name
|
14
|
+
|
15
|
+
rescue ActiveRecord::ActiveRecordError => ex
|
16
|
+
say "Ignoring the exception: #{ ex }"
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def down
|
22
|
+
# nothing to do
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def old_schema_info_table_name
|
29
|
+
ActiveRecord::Base.table_name_prefix + "schema_info" + ActiveRecord::Base.table_name_suffix
|
30
|
+
end
|
31
|
+
|
32
|
+
def old_migration_number
|
33
|
+
@old_migration_number ||= ActiveRecord::Base.connection.select_one("SELECT version FROM #{old_schema_info_table_name}")['version'].to_i rescue 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def migration_numbers
|
37
|
+
Dir["#{RAILS_ROOT}/db/migrate/[0-9]*_*.rb"].collect { |file| file[/(\d+)_[^\/]+\.rb$/][$1].to_i }.sort
|
38
|
+
end
|
39
|
+
|
40
|
+
def mark_already_run_migration(migration_number)
|
41
|
+
ActiveRecord::Base.connection.execute("INSERT INTO #{ActiveRecord::Migrator.schema_info_table_name} VALUES(#{migration_number}, NOW())")
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
def migration_numbers
|
4
|
+
Dir["#{RAILS_ROOT}/db/migrate/[0-9]*_*.rb"].collect { |file| file[/(\d+)_[^\/]+\.rb$/][$1].to_i }.sort
|
5
|
+
end
|
6
|
+
|
7
|
+
def previous_migration
|
8
|
+
raise "No migrations have been run yet" if ActiveRecord::Migrator.current_version == 0
|
9
|
+
return "0" if migration_numbers.first == ActiveRecord::Migrator.current_version
|
10
|
+
"#{migration_numbers.fetch(migration_numbers.index(ActiveRecord::Migrator.current_version) - 1)}" rescue nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def next_migration
|
14
|
+
raise "Already at last migration" if ActiveRecord::Migrator.current_version == migration_numbers.last
|
15
|
+
return "#{migration_numbers.first}" if ActiveRecord::Migrator.current_version == 0
|
16
|
+
"#{migration_numbers.fetch(migration_numbers.index(ActiveRecord::Migrator.current_version) + 1)}" rescue nil
|
17
|
+
end
|
18
|
+
|
19
|
+
namespace :enhanced_migrations do
|
20
|
+
task :set_env do
|
21
|
+
ENV["VERSION"] = case ENV["VERSION"]
|
22
|
+
when "prev", "previous"
|
23
|
+
previous_migration
|
24
|
+
when "next"
|
25
|
+
next_migration
|
26
|
+
when "first"
|
27
|
+
"#{migration_numbers.first}"
|
28
|
+
when "last"
|
29
|
+
"#{migration_numbers.last}"
|
30
|
+
when /\d+/
|
31
|
+
ENV["VERSION"]
|
32
|
+
else
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
task 'db:migrate' => 'enhanced_migrations:set_env'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
# Mix-in for capturing standard output.
|
4
|
+
module CaptureStdout
|
5
|
+
def capture_stdout
|
6
|
+
s = StringIO.new
|
7
|
+
oldstdout = $stdout
|
8
|
+
$stdout = s
|
9
|
+
yield
|
10
|
+
s.string
|
11
|
+
ensure
|
12
|
+
$stdout = oldstdout
|
13
|
+
end
|
14
|
+
|
15
|
+
def capture_stderr
|
16
|
+
s = StringIO.new
|
17
|
+
oldstderr = $stderr
|
18
|
+
$stderr = s
|
19
|
+
yield
|
20
|
+
s.string
|
21
|
+
ensure
|
22
|
+
$stderr = oldstderr
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class MigrationTest < Test::Unit::TestCase
|
4
|
+
include CaptureStdout
|
5
|
+
|
6
|
+
def setup
|
7
|
+
ENV["RAILS_ENV"] = "test"
|
8
|
+
capture_stdout do
|
9
|
+
Rake.application.handle_options
|
10
|
+
Rake.application.load_rakefile
|
11
|
+
end
|
12
|
+
ENV.delete("VERSION") if ENV["VERSION"]
|
13
|
+
|
14
|
+
migrate_db
|
15
|
+
@connection = ActiveRecord::Base.connection
|
16
|
+
end
|
17
|
+
|
18
|
+
def teardown
|
19
|
+
ENV["VERSION"] = "0"
|
20
|
+
migrate_db
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_migrations_ran
|
24
|
+
recipes = @connection.select_all("SELECT * FROM recipes")
|
25
|
+
assert recipes.length > 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_previous_migration_number_runs
|
29
|
+
# Simulate a migration with a lower migration number having not been run yet
|
30
|
+
@connection.execute "DELETE FROM migrations_info WHERE id = '1193800624'"
|
31
|
+
@connection.execute "DELETE FROM recipes WHERE owner = 'user1'"
|
32
|
+
recipes = @connection.select_all("SELECT * FROM recipes WHERE owner = 'user1'")
|
33
|
+
assert recipes.length == 0
|
34
|
+
|
35
|
+
migrate_db
|
36
|
+
recipes = @connection.select_all("SELECT * FROM recipes WHERE owner = 'user1'")
|
37
|
+
assert recipes.length > 0
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_migrate_previous
|
41
|
+
ENV["VERSION"] = "previous"
|
42
|
+
migrate_db
|
43
|
+
|
44
|
+
recipes = @connection.select_all("SELECT * FROM recipes WHERE owner = 'user2'")
|
45
|
+
assert recipes.length == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_migrate_next
|
49
|
+
test_migrate_previous
|
50
|
+
ENV["VERSION"] = "next"
|
51
|
+
migrate_db
|
52
|
+
|
53
|
+
recipes = @connection.select_all("SELECT * FROM recipes WHERE owner = 'user2'")
|
54
|
+
assert recipes.length > 0
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_migrate_first
|
58
|
+
ENV["VERSION"] = "first"
|
59
|
+
migrate_db
|
60
|
+
|
61
|
+
recipes = @connection.select_all("SELECT * FROM recipes WHERE owner = 'user1'")
|
62
|
+
assert recipes.length == 0
|
63
|
+
end
|
64
|
+
|
65
|
+
def test_migrate_last
|
66
|
+
test_migrate_first
|
67
|
+
|
68
|
+
ENV["VERSION"] = "last"
|
69
|
+
migrate_db
|
70
|
+
recipes = @connection.select_all("SELECT * FROM recipes WHERE owner = 'user2'")
|
71
|
+
assert recipes.length > 0
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_migrate_previous_at_zero_migration
|
75
|
+
ENV["VERSION"] = "0"
|
76
|
+
migrate_db
|
77
|
+
|
78
|
+
ENV["VERSION"] = "previous"
|
79
|
+
assert_raises(RuntimeError) { migrate_db }
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_migrate_next_at_last_migration
|
83
|
+
ENV["VERSION"] = "next"
|
84
|
+
assert_raises(RuntimeError) { migrate_db }
|
85
|
+
end
|
86
|
+
|
87
|
+
def test_schema_dump
|
88
|
+
assert ActiveRecord::Base.connection.dump_schema_information.length == 3
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def migrate_db
|
94
|
+
reset_task_invocation('db:migrate')
|
95
|
+
capture_stdout do
|
96
|
+
Rake::Task['db:migrate'].invoke
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def reset_task_invocation(task)
|
101
|
+
Rake::Task['enhanced_migrations:set_env'].instance_variable_set(:@already_invoked, false)
|
102
|
+
Rake::Task[task].instance_variable_set(:@already_invoked, false)
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
ENV["RAILS_ENV"] = "test"
|
2
|
+
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
|
3
|
+
require File.expand_path(File.dirname(__FILE__) + "/../lib/enhanced_migrations")
|
4
|
+
require 'test/unit'
|
5
|
+
require 'test_help'
|
6
|
+
require File.dirname(__FILE__) + '/capture_stdout'
|
7
|
+
|
8
|
+
Dir[File.dirname(__FILE__) + '/../tasks/*.rake'].each { |rake| load rake }
|
9
|
+
|
10
|
+
class Test::Unit::TestCase
|
11
|
+
self.use_transactional_fixtures = true if self.respond_to?(:use_transactional_fixtures)
|
12
|
+
self.use_instantiated_fixtures = false if self.respond_to?(:use_instantiated_fixtures)
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
rubygems_version: 0.9.4
|
3
|
+
specification_version: 1
|
4
|
+
name: enhanced_migrations
|
5
|
+
version: !ruby/object:Gem::Version
|
6
|
+
version: 1.2.0
|
7
|
+
date: 2007-11-06 00:00:00 -05:00
|
8
|
+
summary: Rails Enhanced Migrations
|
9
|
+
require_paths:
|
10
|
+
- lib
|
11
|
+
email: rails-trunk@revolution.com
|
12
|
+
homepage:
|
13
|
+
rubyforge_project:
|
14
|
+
description:
|
15
|
+
autorequire: enhanced_migrations
|
16
|
+
default_executable:
|
17
|
+
bindir: bin
|
18
|
+
has_rdoc: false
|
19
|
+
required_ruby_version: !ruby/object:Gem::Version::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">"
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 0.0.0
|
24
|
+
version:
|
25
|
+
platform: ruby
|
26
|
+
signing_key:
|
27
|
+
cert_chain:
|
28
|
+
post_install_message:
|
29
|
+
authors:
|
30
|
+
- RHG Team
|
31
|
+
files:
|
32
|
+
- Changelog
|
33
|
+
- MIT-LICENSE
|
34
|
+
- Rakefile
|
35
|
+
- README
|
36
|
+
- config/boot.rb
|
37
|
+
- config/database.yml
|
38
|
+
- config/environment.rb
|
39
|
+
- config/environments
|
40
|
+
- config/routes.rb
|
41
|
+
- config/environments/development.rb
|
42
|
+
- config/environments/production.rb
|
43
|
+
- config/environments/test.rb
|
44
|
+
- db/migrate
|
45
|
+
- db/migrate/1193798832_create_recipes_table.rb
|
46
|
+
- db/migrate/1193800624_add_recipes_for_user1.rb
|
47
|
+
- db/migrate/1193842239_add_recipes_for_user2.rb
|
48
|
+
- lib/enhanced_migrations.rb
|
49
|
+
- migrations/000_run_migrations_marker.rb
|
50
|
+
- tasks/enhanced_migrations.rake
|
51
|
+
- test/capture_stdout.rb
|
52
|
+
- test/migration_test.rb
|
53
|
+
- test/test_helper.rb
|
54
|
+
test_files:
|
55
|
+
- test/migration_test.rb
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
extra_rdoc_files:
|
59
|
+
- README
|
60
|
+
- Changelog
|
61
|
+
executables: []
|
62
|
+
|
63
|
+
extensions: []
|
64
|
+
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
dependencies:
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: rails
|
70
|
+
version_requirement:
|
71
|
+
version_requirements: !ruby/object:Gem::Version::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 1.1.6
|
76
|
+
version:
|