migration_collapser 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT_LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009 Scott Taylor (smtlaissezfaire) <scott@railsnewbie.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,21 @@
1
+ = Migration Collapser
2
+
3
+ Collapse your rails migrations
4
+
5
+ == Installation
6
+
7
+ gem install migration_collapser
8
+
9
+ == Usage
10
+
11
+ First, load your production database:
12
+
13
+ mysql -u root my_dev_db < my_production_db.sql
14
+
15
+ Dump your production database (without data) in db/migrate/initial_schema.sql
16
+
17
+ mysqldump -u root --no-data my_dev_db > db/migrate/initial_schema.sql
18
+
19
+ Run the migration collapser from RAILS_ROOT:
20
+
21
+ collapse_migrations
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+
2
+ Dir.glob(File.dirname(__FILE__) + "/rake_tasks/**/**").each do |file|
3
+ require file
4
+ end
5
+
6
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/migration_collapser")
4
+ MigrationCollapser::Runner.run
@@ -0,0 +1,64 @@
1
+ module MigrationCollapser
2
+ class FileReplacer
3
+ PATTERN = /(\d+)\_.+\.rb/
4
+
5
+ def initialize(revision)
6
+ self.revision = revision
7
+ end
8
+
9
+ attr_reader :revision
10
+
11
+ def revision=(num)
12
+ @revision = num.to_i
13
+ end
14
+
15
+ def replace
16
+ delete_files
17
+ create_migration
18
+ end
19
+
20
+ def delete_files
21
+ files = Dir.glob("db/migrate/*.rb")
22
+ file_map = new_migration_map(files)
23
+
24
+ check_files(file_map)
25
+
26
+ file_map.each do |file, number|
27
+ if number <= revision
28
+ FileUtils.rm(file)
29
+ end
30
+ end
31
+ end
32
+
33
+ def create_migration
34
+ cp "templates/initial_schema_migration.rb", "db/migrate/#{revision}_initial_schema.rb"
35
+ end
36
+
37
+ private
38
+
39
+ def cp(template, path)
40
+ FileUtils.cp(File.dirname(__FILE__) + "/#{template}", path)
41
+ end
42
+
43
+ def check_files(file_map)
44
+ raise "No files in db/migrate" if file_map.empty?
45
+
46
+ if file_map.none? { |_, number| number == revision }
47
+ raise "No migration matching revision #{revision}"
48
+ end
49
+ end
50
+
51
+ def new_migration_map(files)
52
+ hash = {}
53
+
54
+ files.each do |file|
55
+ file =~ PATTERN
56
+ number = $1.to_i
57
+
58
+ hash[file] = number
59
+ end
60
+
61
+ hash
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,19 @@
1
+ module MigrationCollapser
2
+ module RailsLoader
3
+ class << self
4
+ def load
5
+ require expand_path(Dir.getwd, "config", "environment")
6
+ end
7
+
8
+ private
9
+
10
+ def expand_path(*dirs)
11
+ File.expand_path(join(*dirs))
12
+ end
13
+
14
+ def join(*dirs)
15
+ File.join(*dirs)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_record'
2
+
3
+ module MigrationCollapser
4
+ class RevisionFinder
5
+ class SchemaInfo < ActiveRecord::Base
6
+ set_table_name "schema_migrations"
7
+ end
8
+
9
+ def self.find
10
+ SchemaInfo.find(:first, :order => "version DESC").version
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ module MigrationCollapser
2
+ class Runner
3
+ def self.run
4
+ RailsLoader.load
5
+ revision_num = RevisionFinder.find
6
+ FileReplacer.new(revision_num).replace
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,70 @@
1
+ class InitialSchema < ActiveRecord::Migration
2
+ class << self
3
+ def up
4
+ class << connection = ActiveRecord::Base.connection
5
+ def username
6
+ @config[:username]
7
+ end
8
+
9
+ def host
10
+ @config[:host]
11
+ end
12
+ end
13
+
14
+ file = File.read(File.dirname(__FILE__) + "/initial_schema.sql")
15
+
16
+ file.split(";").each do |sql_line|
17
+ execute(sql_line, true)
18
+ end
19
+ end
20
+
21
+ def down
22
+ drop_views
23
+ drop_tables
24
+ end
25
+
26
+ private
27
+
28
+ def execute(sql_string, verbose = false)
29
+ if verbose
30
+ puts "* Executing: #{sql_string}"
31
+ end
32
+
33
+ ActiveRecord::Base.connection.execute(sql_string)
34
+ end
35
+
36
+ def drop_tables
37
+ tables.each do |table|
38
+ drop_table(table)
39
+ end
40
+ end
41
+
42
+ def drop_views
43
+ views.each do |view|
44
+ drop_view(view)
45
+ end
46
+ end
47
+
48
+ def drop_table(table_name)
49
+ puts "* Dropping table: #{table_name}"
50
+ execute "DROP TABLE `#{table_name}`"
51
+ end
52
+
53
+ def drop_view(table_name)
54
+ puts "* Dropping view: #{table_name}"
55
+ execute "DROP VIEW `#{table_name}`"
56
+ end
57
+
58
+ def tables
59
+ @tables ||= all_tables - ["schema_migrations"]
60
+ end
61
+
62
+ def all_tables
63
+ execute("SHOW FULL TABLES WHERE table_type = 'VIEW'").all_hashes.map { |x| x.values.last }
64
+ end
65
+
66
+ def views
67
+ @views ||= execute("SHOW FULL TABLES WHERE table_type = 'VIEW'").all_hashes.map { |x| x.values.last }
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,9 @@
1
+ module MigrationCollapser
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 1
5
+ TINY = 0
6
+
7
+ STRING = "#{MAJOR}.#{MINOR}.#{TINY}"
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ require "using"
2
+
3
+ module MigrationCollapser
4
+ extend Using
5
+
6
+ using :Version
7
+ using :RevisionFinder
8
+ using :RailsLoader
9
+ using :FileReplacer
10
+ using :Runner
11
+ end
@@ -0,0 +1,10 @@
1
+ desc "Feel the pain of my code, and submit a refactoring patch"
2
+ task :flog do
3
+ puts %x(find lib | grep ".rb$" | xargs flog)
4
+ end
5
+
6
+ task :flog_to_disk => :create_doc_directory do
7
+ puts "Flogging..."
8
+ %x(find lib | grep ".rb$" | xargs flog > doc/flog.txt)
9
+ puts "Done Flogging...\n"
10
+ end
data/rake_tasks/gem.rb ADDED
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/migration_collapser")
2
+
3
+ begin
4
+ require 'jeweler'
5
+
6
+ def set_version_for_jewler
7
+ version = MigrationCollapser::Version::STRING
8
+
9
+ File.open(File.expand_path(File.dirname(__FILE__) + "/../VERSION"), "w") do |f|
10
+ f << version
11
+ end
12
+ end
13
+
14
+ Jeweler::Tasks.new do |gemspec|
15
+ set_version_for_jewler
16
+
17
+ gemspec.name = "migration_collapser"
18
+ gemspec.summary = "Collapse rails migrations"
19
+ gemspec.description = "Collapse migrations already run in production"
20
+ gemspec.email = "scott@railsnewbie.com"
21
+ gemspec.homepage = "http://github.com/smtlaissezfaire/migration_collapser"
22
+ gemspec.authors = ["Scott Taylor"]
23
+ gemspec.add_dependency "using"
24
+ end
25
+ rescue LoadError
26
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
27
+ end
@@ -0,0 +1,16 @@
1
+ require 'rake'
2
+ require 'hanna/rdoctask'
3
+
4
+ DOC_DIRECTORY = File.dirname(__FILE__) + "/../doc"
5
+
6
+ Rake::RDocTask.new do |rdoc|
7
+ rdoc.rdoc_dir = DOC_DIRECTORY
8
+ rdoc.title = 'migration_collapser'
9
+ rdoc.options << '--line-numbers' << '--inline-source'
10
+
11
+ rdoc.options << '--webcvs=http://github.com/smtlaissezfaire/migration_collapser'
12
+
13
+ ["README.rdoc", "GPL_LICENSE", "MIT_LICENSE", "lib/**/*.rb"].each do |file|
14
+ rdoc.rdoc_files.include(file)
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ require 'spec/rake/spectask'
2
+ require 'spec/rake/verify_rcov'
3
+
4
+ desc 'Run the specs'
5
+ Spec::Rake::SpecTask.new do |t|
6
+ t.warning = false
7
+ t.spec_opts = ["--color"]
8
+ end
9
+
10
+ desc "Create the html specdoc"
11
+ Spec::Rake::SpecTask.new(:specdoc) do |t|
12
+ t.spec_opts = ["--format", "html:doc/specdoc.html"]
13
+ end
14
+
15
+ desc "Run all examples with RCov"
16
+ Spec::Rake::SpecTask.new(:rcov) do |t|
17
+ t.rcov = true
18
+ t.rcov_opts = ['--exclude', 'spec', "--exclude", "gems"]
19
+ t.rcov_dir = "doc/rcov"
20
+ end
@@ -0,0 +1,16 @@
1
+ def sloc
2
+ `sloccount #{File.dirname(__FILE__)}/../lib #{File.dirname(__FILE__)}/../ext`
3
+ end
4
+
5
+ desc "Output sloccount report. You'll need sloccount installed."
6
+ task :sloc do
7
+ puts "Counting lines of code"
8
+ puts sloc
9
+ end
10
+
11
+ desc "Write sloccount report"
12
+ task :output_sloc => :create_doc_directory do
13
+ File.open(File.dirname(__FILE__) + "/doc/lines_of_code.txt", "w") do |f|
14
+ f << sloc
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ # Build the TAGS file for Emacs
2
+ # Taken with slight modifications from
3
+ # http://blog.lathi.net/articles/2007/11/07/navigating-your-projects-in-emacs
4
+ #
5
+ # Thanks Jim Weirich
6
+
7
+ module Emacs
8
+ module Tags
9
+ def self.ruby_files
10
+ @ruby_files ||= FileList['**/*.rb'].exclude("pkg")
11
+ end
12
+ end
13
+ end
14
+
15
+ namespace :tags do
16
+ task :emacs do
17
+ puts "Making Emacs TAGS file"
18
+ sh "ctags -e #{Emacs::Tags.ruby_files}", :verbose => false
19
+ end
20
+ end
21
+
22
+ desc "Build the emacs tags file"
23
+ task :tags => ["tags:emacs"]
@@ -0,0 +1,130 @@
1
+ require "spec_helper"
2
+ require "fakefs/spec_helpers"
3
+
4
+ module MigrationCollapser
5
+ describe FileReplacer do
6
+ describe "replace" do
7
+ before do
8
+ @replacer = FileReplacer.new("revision")
9
+ @replacer.stub!(:delete_files)
10
+ @replacer.stub!(:create_migration)
11
+ end
12
+
13
+ it "should call delete_files" do
14
+ @replacer.should_receive(:delete_files)
15
+ @replacer.replace
16
+ end
17
+
18
+ it "should call create_migration" do
19
+ @replacer.should_receive(:create_migration)
20
+ @replacer.replace
21
+ end
22
+ end
23
+
24
+ def setup_rails
25
+ Dir.mkdir "/rails_root"
26
+ Dir.mkdir "/rails_root/db"
27
+ Dir.mkdir "/rails_root/db/migrate"
28
+
29
+ Dir.chdir "/rails_root"
30
+ end
31
+
32
+ describe "deleting files" do
33
+ include FakeFS::SpecHelpers
34
+
35
+ before do
36
+ setup_rails
37
+
38
+ @revision = 123456
39
+ @replacer = FileReplacer.new(@revision)
40
+ end
41
+
42
+ it "should raise an error if it has no files" do
43
+ lambda {
44
+ @replacer.delete_files
45
+ }.should raise_error("No files in db/migrate")
46
+ end
47
+
48
+ it "should delete the file if it it exists" do
49
+ FileUtils.touch "/rails_root/db/migrate/123456_my_migration.rb"
50
+ @replacer.delete_files
51
+ File.exists?("/rails_root/db/migrate/123456_my_migration.rb").should be_false
52
+ end
53
+
54
+ it "should delete the migration with any name" do
55
+ FileUtils.touch "/rails_root/db/migrate/123456_foo_bar.rb"
56
+ @replacer.delete_files
57
+ File.exists?("/rails_root/db/migrate/123456_foo_bar.rb").should be_false
58
+ end
59
+
60
+ it "should delete the migration with the correct number" do
61
+ @replacer.revision = 123
62
+ FileUtils.touch "/rails_root/db/migrate/123_foo_bar.rb"
63
+ @replacer.delete_files
64
+ File.exists?("/rails_root/db/migrate/123_foo_bar.rb").should be_false
65
+ end
66
+
67
+ it "should delete all the migrations before (and including) the given number" do
68
+ @replacer.revision = 002
69
+
70
+ FileUtils.touch "/rails_root/db/migrate/001_foo_bar.rb"
71
+ FileUtils.touch "/rails_root/db/migrate/002_foo_bar.rb"
72
+
73
+ @replacer.delete_files
74
+
75
+ File.exists?("/rails_root/db/migrate/001_foo_bar.rb").should be_false
76
+ File.exists?("/rails_root/db/migrate/002_foo_bar.rb").should be_false
77
+ end
78
+
79
+ it "should not delete migrations before the number" do
80
+ @replacer.revision = 2
81
+
82
+ FileUtils.touch "/rails_root/db/migrate/001_foo_bar.rb"
83
+ FileUtils.touch "/rails_root/db/migrate/002_foo_bar.rb"
84
+ FileUtils.touch "/rails_root/db/migrate/003_foo_bar.rb"
85
+
86
+ @replacer.delete_files
87
+
88
+ File.exists?("/rails_root/db/migrate/001_foo_bar.rb").should be_false
89
+ File.exists?("/rails_root/db/migrate/002_foo_bar.rb").should be_false
90
+ File.exists?("/rails_root/db/migrate/003_foo_bar.rb").should be_true
91
+ end
92
+
93
+ it "should raise an error if the file does not exist" do
94
+ FileUtils.touch "/rails_root/db/migrate/0001_my_migration.rb"
95
+
96
+ @replacer.revision = 123
97
+
98
+ lambda {
99
+ @replacer.delete_files
100
+ }.should raise_error
101
+ end
102
+ end
103
+
104
+ describe "replacing the migration" do
105
+ before do
106
+ @replacer = FileReplacer.new(1234)
107
+ @file = File.expand_path(File.dirname(__FILE__) + "/../../lib/migration_collapser/templates/initial_schema_migration.rb")
108
+ end
109
+
110
+ it "should copy the contents of the default migration" do
111
+ FileUtils.should_receive(:cp).with(@file, "db/migrate/999_initial_schema.rb")
112
+ @replacer.revision = 999
113
+
114
+ @replacer.create_migration
115
+ end
116
+ end
117
+
118
+ describe "revision" do
119
+ it "should have it as an int when assigned in the constructor" do
120
+ FileReplacer.new("1234").revision.should == 1234
121
+ end
122
+
123
+ it "should cast it to an int when provided through the accessor" do
124
+ replacer = FileReplacer.new("1234")
125
+ replacer.revision = "1111"
126
+ replacer.revision.should == 1111
127
+ end
128
+ end
129
+ end
130
+ end