migration_collapser 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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