Skip to content

Commit

Permalink
migrate automatically
Browse files Browse the repository at this point in the history
  • Loading branch information
pjhyett committed Mar 16, 2008
0 parents commit 73cff38
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 0 deletions.
20 changes: 20 additions & 0 deletions MIT-LICENSE
@@ -0,0 +1,20 @@
Copyright (c) 2007 PJ Hyett

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
51 changes: 51 additions & 0 deletions README
@@ -0,0 +1,51 @@
== AutoMigrations

Forget migrations, auto-migrate!


== Usage

Write out your schema (or use an existing one)

$ cat db/schema.rb

ActiveRecord::Schema.define do

create_table :posts do |t|
t.string :title
t.text :body
t.timestamps
end

end

$ rake db:auto:migrate

Created posts table

...a few days later

$ cat db/schema.rb

ActiveRecord::Schema.define do

create_table :posts do |t|
t.string :title
t.text :content
t.timestamps
end

end

$ rake db:auto:migrate
-- add_column("posts", :content, :text)
-> 0.0307s
-- remove_column("posts", "body")
-> 0.0311s

Found a bug? Sweet. Add it at the Lighthouse:
http://err.lighthouseapp.com/projects/466-plugins/tickets/new

Feature requests are welcome.

* PJ Hyett [ pjhyett@gmail.com ]
24 changes: 24 additions & 0 deletions Rakefile
@@ -0,0 +1,24 @@
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'

desc 'Default: run unit tests.'
task :default => :test

desc 'Test the auto_migrations plugin.'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.pattern = 'test/**/*_test.rb'
t.verbose = true
end

desc 'Generate documentation for the auto_migrations plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
files = ['README', 'LICENSE', 'lib/**/*.rb']
rdoc.rdoc_files.add(files)
rdoc.main = "README" # page to start on
rdoc.title = "auto_migrations"
rdoc.template = File.exists?(t="/Users/chris/ruby/projects/err/rock/template.rb") ? t : "/var/www/rock/template.rb"
rdoc.rdoc_dir = 'doc' # rdoc output folder
rdoc.options << '--inline-source'
end
2 changes: 2 additions & 0 deletions init.rb
@@ -0,0 +1,2 @@
require 'auto_migrations'
ActiveRecord::Migration.send :include, AutoMigrations
136 changes: 136 additions & 0 deletions lib/auto_migrations.rb
@@ -0,0 +1,136 @@
module AutoMigrations

def self.run
# Turn off schema_info code for auto-migration
class << ActiveRecord::Schema
alias :old_define :define
def define(info={}, &block) instance_eval(&block) end
end

load(File.join(RAILS_ROOT, 'db', 'schema.rb'))
ActiveRecord::Migration.drop_unused_tables
ActiveRecord::Migration.drop_unused_indexes

class << ActiveRecord::Schema
alias :define :old_define
end
end

def self.schema_to_migration
schema = File.read(File.join(RAILS_ROOT, "db", "schema.rb"))
schema.gsub!(/#(.)+\n/, '')
schema.sub!(/ActiveRecord::Schema.define(.+)do[ ]?\n/, '')
schema.gsub!(/^/, ' ')
schema = "class InitialSchema < ActiveRecord::Migration\n def self.up\n" + schema
schema << "\n def self.down\n"
schema << (ActiveRecord::Base.connection.tables - ["schema_info"]).map do |table|
" drop_table :#{table}\n"
end.join
schema << " end\nend\n"
migration_file = File.join(RAILS_ROOT, "db", "migrate", "001_initial_schema.rb")
File.open(migration_file, "w") { |f| f << schema }
puts "Migration created at db/migrate/001_initial_schema.rb"
end

def self.included(base)
base.extend ClassMethods
class << base
cattr_accessor :tables_in_schema, :indexes_in_schema
self.tables_in_schema, self.indexes_in_schema = [], []
alias_method_chain :method_missing, :auto_migration
end
end

module ClassMethods

def method_missing_with_auto_migration(method, *args, &block)
case method
when :create_table
auto_create_table(method, *args, &block)
when :add_index
auto_add_index(method, *args, &block)
else
method_missing_without_auto_migration(method, *args, &block)
end
end

def auto_create_table(method, *args, &block)
table_name = args.shift.to_s
options = args.pop || {}

(self.tables_in_schema ||= []) << table_name

# Table doesn't exist, create it
unless ActiveRecord::Base.connection.tables.include?(table_name)
return method_missing_without_auto_migration(method, *[table_name, options], &block)
end

# Grab database columns
fields_in_db = ActiveRecord::Base.connection.columns(table_name).inject({}) do |hash, column|
hash[column.name] = column
hash
end

# Grab schema columns (lifted from active_record/connection_adapters/abstract/schema_statements.rb)
table_definition = ActiveRecord::ConnectionAdapters::TableDefinition.new(ActiveRecord::Base.connection)
table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
yield table_definition
fields_in_schema = table_definition.columns.inject({}) do |hash, column|
hash[column.name.to_s] = column
hash
end

# Add fields to db new to schema
(fields_in_schema.keys - fields_in_db.keys).each do |field|
column = fields_in_schema[field]
add_column table_name, column.name, column.type.to_sym, :limit => column.limit, :precision => column.precision,
:scale => column.scale, :default => column.default, :null => column.null
end

# Remove fields from db no longer in schema
(fields_in_db.keys - fields_in_schema.keys & fields_in_db.keys).each do |field|
column = fields_in_db[field]
remove_column table_name, column.name
end

# Change field type if schema is different from db
(fields_in_schema.keys & fields_in_db.keys).each do |field|
if (field != 'id') && (fields_in_schema[field].type.to_sym != fields_in_db[field].type.to_sym)
change_column table_name, fields_in_schema[field].name.to_sym, fields_in_schema[field].type.to_sym
end
end
end

def auto_add_index(method, *args, &block)
table_name = args.shift.to_s
fields = Array(args.shift).map(&:to_s)
options = args.shift

index_name = options[:name] if options
index_name ||= "index_#{table_name}_on_#{fields.join('_and_')}"

(self.indexes_in_schema ||= []) << index_name

unless ActiveRecord::Base.connection.indexes(table_name).detect { |i| i.name == index_name }
method_missing_without_auto_migration(method, *[table_name, fields, options], &block)
end
end

def drop_unused_tables
(ActiveRecord::Base.connection.tables - tables_in_schema - ["schema_info"]).each do |table|
drop_table table
end
end

def drop_unused_indexes
tables_in_schema.each do |table_name|
indexes_in_db = ActiveRecord::Base.connection.indexes(table_name).map(&:name)
(indexes_in_db - indexes_in_schema & indexes_in_db).each do |index_name|
remove_index table_name, :name => index_name
end
end
end

end

end
15 changes: 15 additions & 0 deletions tasks/auto_migrations_tasks.rake
@@ -0,0 +1,15 @@
namespace :db do
namespace :auto do
desc "Use schema.rb to auto-migrate"
task :migrate => :environment do
AutoMigrations.run
end
end

namespace :schema do
desc "Create migration from schema.rb"
task :to_migration => :environment do
AutoMigrations.schema_to_migration
end
end
end
8 changes: 8 additions & 0 deletions test/auto_migrations_test.rb
@@ -0,0 +1,8 @@
require 'test/unit'

class AutoMigrationsTest < Test::Unit::TestCase
# Replace this with your real tests.
def test_this_plugin
flunk
end
end

0 comments on commit 73cff38

Please sign in to comment.