dm-migrations 0.9.2 → 0.9.3
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/History.txt +1 -0
- data/Manifest.txt +38 -0
- data/{README → README.txt} +1 -2
- data/Rakefile +22 -29
- data/db/migrations/1_create_people_table.rb +12 -0
- data/db/migrations/2_add_dob_to_people.rb +13 -0
- data/db/migrations/config.rb +4 -0
- data/examples/sample_migration.rb +61 -0
- data/examples/sample_migration_spec.rb +46 -0
- data/lib/dm-migrations/version.rb +5 -0
- data/lib/migration.rb +10 -12
- data/lib/sql.rb +3 -103
- data/lib/sql/postgresql.rb +10 -10
- data/lib/sql/sqlite3.rb +1 -8
- data/lib/sql/table_creator.rb +55 -0
- data/lib/sql/table_modifier.rb +53 -0
- data/spec/integration/migration_runner_spec.rb +58 -55
- data/spec/integration/migration_spec.rb +99 -99
- data/spec/integration/sql_spec.rb +118 -108
- data/spec/spec.opts +2 -1
- data/spec/spec_helper.rb +4 -11
- data/spec/unit/migration_spec.rb +447 -0
- data/spec/unit/sql/column_spec.rb +18 -0
- data/spec/unit/sql/postgresql_spec.rb +99 -0
- data/spec/unit/sql/sqlite3_extensions_spec.rb +109 -0
- data/spec/unit/sql/table_creator_spec.rb +93 -0
- data/spec/unit/sql/table_modifier_spec.rb +52 -0
- data/spec/unit/sql/table_spec.rb +31 -0
- data/spec/unit/sql_spec.rb +10 -0
- metadata +48 -17
data/lib/sql/postgresql.rb
CHANGED
@@ -9,9 +9,6 @@ module SQL
|
|
9
9
|
SQL::Postgresql::Table.new(self, table_name)
|
10
10
|
end
|
11
11
|
|
12
|
-
def drop_database
|
13
|
-
end
|
14
|
-
|
15
12
|
def recreate_database
|
16
13
|
execute "DROP SCHEMA IF EXISTS test CASCADE"
|
17
14
|
execute "CREATE SCHEMA test"
|
@@ -37,19 +34,21 @@ module SQL
|
|
37
34
|
|
38
35
|
class Table < SQL::Table
|
39
36
|
def initialize(adapter, table_name)
|
37
|
+
@adapter, @name = adapter, table_name
|
40
38
|
@columns = []
|
41
39
|
adapter.query_table(table_name).each do |col_struct|
|
42
40
|
@columns << SQL::Postgresql::Column.new(col_struct)
|
43
41
|
end
|
44
42
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
43
|
+
query_column_constraints
|
44
|
+
end
|
45
|
+
|
46
|
+
def query_column_constraints
|
47
|
+
@adapter.query(
|
48
|
+
"SELECT * FROM information_schema.table_constraints WHERE table_name='#{@name}' AND table_schema=current_schema()"
|
49
49
|
).each do |table_constraint|
|
50
|
-
|
51
|
-
|
52
|
-
"SELECT * FROM information_schema.constraint_column_usage WHERE table_name='#{table_name}' AND table_schema=current_schema()"
|
50
|
+
@adapter.query(
|
51
|
+
"SELECT * FROM information_schema.constraint_column_usage WHERE constraint_name='#{table_constraint.constraint_name}' AND table_schema=current_schema()"
|
53
52
|
).each do |constrained_column|
|
54
53
|
@columns.each do |column|
|
55
54
|
if column.name == constrained_column.column_name
|
@@ -61,6 +60,7 @@ module SQL
|
|
61
60
|
end
|
62
61
|
end
|
63
62
|
end
|
63
|
+
|
64
64
|
end
|
65
65
|
|
66
66
|
end
|
data/lib/sql/sqlite3.rb
CHANGED
@@ -8,7 +8,7 @@ module SQL
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def table(table_name)
|
11
|
-
SQL::Table.new(self, table_name)
|
11
|
+
SQL::Sqlite3::Table.new(self, table_name)
|
12
12
|
end
|
13
13
|
|
14
14
|
def recreate_database
|
@@ -21,13 +21,6 @@ module SQL
|
|
21
21
|
true
|
22
22
|
end
|
23
23
|
|
24
|
-
# TODO: move to dm-more/dm-migrations
|
25
|
-
def property_schema_statement(schema)
|
26
|
-
statement = super
|
27
|
-
statement << ' PRIMARY KEY AUTOINCREMENT' if supports_serial? && schema[:serial]
|
28
|
-
statement
|
29
|
-
end
|
30
|
-
|
31
24
|
class Table < SQL::Table
|
32
25
|
def initialize(adapter, table_name)
|
33
26
|
@columns = []
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module SQL
|
2
|
+
class TableCreator
|
3
|
+
attr_accessor :table_name, :opts
|
4
|
+
|
5
|
+
def initialize(adapter, table_name, opts = {}, &block)
|
6
|
+
@adapter = adapter
|
7
|
+
@table_name = table_name.to_s
|
8
|
+
@opts = opts
|
9
|
+
|
10
|
+
@columns = []
|
11
|
+
|
12
|
+
self.instance_eval &block
|
13
|
+
end
|
14
|
+
|
15
|
+
def quoted_table_name
|
16
|
+
@adapter.send(:quote_table_name, table_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def column(name, type, opts = {})
|
20
|
+
@columns << Column.new(@adapter, name, type, opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_sql
|
24
|
+
"CREATE TABLE #{quoted_table_name} (#{@columns.map{ |c| c.to_sql }.join(', ')})"
|
25
|
+
end
|
26
|
+
|
27
|
+
class Column
|
28
|
+
attr_accessor :name, :type
|
29
|
+
|
30
|
+
def initialize(adapter, name, type, opts = {})
|
31
|
+
@adapter = adapter
|
32
|
+
@name = name.to_s
|
33
|
+
@opts = opts
|
34
|
+
@type = build_type(type)
|
35
|
+
end
|
36
|
+
|
37
|
+
def build_type(type_class)
|
38
|
+
schema = {:name => @name, :quote_column_name => quoted_name}.merge(@opts)
|
39
|
+
schema = @adapter.class.type_map[type_class].merge(schema)
|
40
|
+
@adapter.property_schema_statement(schema)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_sql
|
44
|
+
type
|
45
|
+
end
|
46
|
+
|
47
|
+
def quoted_name
|
48
|
+
@adapter.send(:quote_column_name, name)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module SQL
|
2
|
+
class TableModifier
|
3
|
+
attr_accessor :table_name, :opts, :statements, :adapter
|
4
|
+
|
5
|
+
def initialize(adapter, table_name, opts = {}, &block)
|
6
|
+
@adapter = adapter
|
7
|
+
@table_name = table_name.to_s
|
8
|
+
@opts = (opts)
|
9
|
+
|
10
|
+
@statements = []
|
11
|
+
|
12
|
+
self.instance_eval &block
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_column(name, type, opts = {})
|
16
|
+
column = SQL::TableCreator::Column.new(@adapter, name, type, opts)
|
17
|
+
@statements << "ALTER TABLE #{quoted_table_name} ADD COLUMN #{column.to_sql}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def drop_column(name)
|
21
|
+
# raise NotImplemented for SQLite3. Can't ALTER TABLE, need to copy table.
|
22
|
+
# We'd have to inspect it, and we can't, since we aren't executing any queries yet.
|
23
|
+
# TODO instead of building the SQL queries when executing the block, create AddColumn,
|
24
|
+
# AlterColumn and DropColumn objects that get #to_sql'd
|
25
|
+
if name.is_a?(Array)
|
26
|
+
name.each{ |n| drop_column(n) }
|
27
|
+
else
|
28
|
+
@statements << "ALTER TABLE #{quoted_table_name} DROP COLUMN #{quote_column_name(name)}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
alias drop_columns drop_column
|
32
|
+
|
33
|
+
def rename_column(name, new_name, opts = {})
|
34
|
+
# raise NotImplemented for SQLite3
|
35
|
+
@statements << "ALTER TABLE #{quoted_table_name} RENAME COLUMN #{quote_column_name(name)} TO #{quote_column_name(new_name)}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def change_column(name, type, opts = {})
|
39
|
+
# raise NotImplemented for SQLite3
|
40
|
+
@statements << "ALTER TABLE #{quoted_table_name} ALTER COLUMN #{quote_column_name(name)} TYPE #{type}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def quote_column_name(name)
|
44
|
+
@adapter.send(:quote_column_name, name.to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
def quoted_table_name
|
48
|
+
@adapter.send(:quote_table_name, table_name)
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
@@ -1,78 +1,81 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
# set up some 'global' setup and teardown tasks
|
13
|
-
before(:each) do
|
14
|
-
migration( 1, :create_people_table) { }
|
15
|
-
end
|
16
|
-
|
17
|
-
after(:each) do
|
18
|
-
@@migrations = []
|
4
|
+
[:sqlite3, :mysql, :postgres].each do |adapter|
|
5
|
+
next unless eval("HAS_#{adapter.to_s.upcase}")
|
6
|
+
describe "Using Adapter #{adapter}, " do
|
7
|
+
describe 'empty migration runner' do
|
8
|
+
it "should return an empty array if no migrations have been defined" do
|
9
|
+
migrations.should be_kind_of(Array)
|
10
|
+
migrations.should have(0).item
|
11
|
+
end
|
19
12
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@@migrations.should be_kind_of(Array)
|
25
|
-
@@migrations.should have(1).item
|
26
|
-
@@migrations.first.name.should == "create_people_table"
|
13
|
+
describe 'migration runnner' do
|
14
|
+
# set up some 'global' setup and teardown tasks
|
15
|
+
before(:each) do
|
16
|
+
migration( 1, :create_people_table) { }
|
27
17
|
end
|
28
18
|
|
29
|
-
|
30
|
-
|
31
|
-
migration( 2, :add_favorite_pet_to_people) { }
|
32
|
-
migration( 3, :add_something_else_to_people) { }
|
33
|
-
@@migrations.should have(4).items
|
19
|
+
after(:each) do
|
20
|
+
@@migrations = []
|
34
21
|
end
|
35
22
|
|
36
|
-
|
37
|
-
lambda { migration( 1, :create_people_table) { } }.should raise_error(RuntimeError, /Migration name conflict/)
|
38
|
-
end
|
23
|
+
describe '#migration' do
|
39
24
|
|
40
|
-
|
25
|
+
it 'should create a new migration object, and add it to the list of migrations' do
|
26
|
+
@@migrations.should be_kind_of(Array)
|
27
|
+
@@migrations.should have(1).item
|
28
|
+
@@migrations.first.name.should == "create_people_table"
|
29
|
+
end
|
41
30
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
31
|
+
it 'should allow multiple migrations to be added' do
|
32
|
+
migration( 2, :add_dob_to_people) { }
|
33
|
+
migration( 2, :add_favorite_pet_to_people) { }
|
34
|
+
migration( 3, :add_something_else_to_people) { }
|
35
|
+
@@migrations.should have(4).items
|
36
|
+
end
|
48
37
|
|
49
|
-
|
50
|
-
|
51
|
-
@@migrations.each do |m|
|
52
|
-
m.should_receive(:perform_up)
|
38
|
+
it 'should raise an error on adding with a duplicated name' do
|
39
|
+
lambda { migration( 1, :create_people_table) { } }.should raise_error(RuntimeError, /Migration name conflict/)
|
53
40
|
end
|
54
|
-
|
41
|
+
|
55
42
|
end
|
56
43
|
|
57
|
-
|
58
|
-
|
59
|
-
|
44
|
+
describe '#migrate_up! and #migrate_down!' do
|
45
|
+
before(:each) do
|
46
|
+
migration( 2, :add_dob_to_people) { }
|
47
|
+
migration( 2, :add_favorite_pet_to_people) { }
|
48
|
+
migration( 3, :add_something_else_to_people) { }
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'calling migrate_up! should migrate up all the migrations' do
|
52
|
+
# add our expectation that migrate_up should be called
|
53
|
+
@@migrations.each do |m|
|
60
54
|
m.should_receive(:perform_up)
|
61
|
-
else
|
62
|
-
m.should_not_receive(:perform_up)
|
63
55
|
end
|
56
|
+
migrate_up!
|
64
57
|
end
|
65
|
-
migrate_up!(2)
|
66
|
-
end
|
67
58
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
59
|
+
it 'calling migrate_up! with an arguement should only migrate to that level' do
|
60
|
+
@@migrations.each do |m|
|
61
|
+
if m.position <= 2
|
62
|
+
m.should_receive(:perform_up)
|
63
|
+
else
|
64
|
+
m.should_not_receive(:perform_up)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
migrate_up!(2)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'calling migrate_down! should migrate down all the migrations' do
|
71
|
+
# add our expectation that migrate_up should be called
|
72
|
+
@@migrations.each do |m|
|
73
|
+
m.should_receive(:perform_down)
|
74
|
+
end
|
75
|
+
migrate_down!
|
72
76
|
end
|
73
|
-
migrate_down!
|
74
|
-
end
|
75
77
|
|
78
|
+
end
|
76
79
|
end
|
77
80
|
end
|
78
81
|
end
|
@@ -1,136 +1,136 @@
|
|
1
1
|
require 'pathname'
|
2
2
|
require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
[:sqlite3, :mysql, :postgres].each do |adapter|
|
5
|
+
next unless eval("HAS_#{adapter.to_s.upcase}")
|
6
|
+
describe "Using Adapter #{adapter}, " do
|
7
|
+
describe DataMapper::Migration, 'interface' do
|
8
|
+
before do
|
9
|
+
@migration = DataMapper::Migration.new(1, :create_people_table, :verbose => false) { }
|
10
|
+
end
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
it "should have a postition attribute" do
|
13
|
+
@migration.should respond_to(:position)
|
14
|
+
@migration.should respond_to(:position=)
|
15
|
+
@migration.position.should == 1
|
16
|
+
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
it "should have a name attribute" do
|
19
|
+
@migration.should respond_to(:name)
|
20
|
+
@migration.should respond_to(:name=)
|
21
|
+
@migration.name.should == :create_people_table
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
+
it "should have a :database option" do
|
25
|
+
DataMapper.setup(:other, "sqlite3://#{Dir.pwd}/migration_other.db")
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
m = DataMapper::Migration.new(2, :create_dogs_table, :database => :other) {}
|
28
|
+
m.instance_variable_get(:@database).name.should == :other
|
29
|
+
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
it "should use the default database by default" do
|
32
|
+
@migration.instance_variable_get(:@database).name.should == :default
|
33
|
+
end
|
32
34
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
35
|
+
it "should have a verbose option" do
|
36
|
+
m = DataMapper::Migration.new(2, :create_dogs_table, :verbose => false) {}
|
37
|
+
m.instance_variable_get(:@verbose).should == false
|
38
|
+
end
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
it "should be verbose by default" do
|
41
|
+
m = DataMapper::Migration.new(2, :create_dogs_table) {}
|
42
|
+
m.instance_variable_get(:@verbose).should == true
|
43
|
+
end
|
42
44
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
it "should be sortable, first by position, then name" do
|
46
|
+
m1 = DataMapper::Migration.new(1, :create_people_table) {}
|
47
|
+
m2 = DataMapper::Migration.new(2, :create_dogs_table) {}
|
48
|
+
m3 = DataMapper::Migration.new(2, :create_cats_table) {}
|
49
|
+
m4 = DataMapper::Migration.new(4, :create_birds_table) {}
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
+
[m1, m2, m3, m4].sort.should == [m1, m3, m2, m4]
|
52
|
+
end
|
51
53
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
54
|
+
if HAS_SQLITE3
|
55
|
+
it "should extend with SQL::Sqlite3 when adapter is Sqlite3Adapter" do
|
56
|
+
migration = DataMapper::Migration.new(1, :sqlite3_adapter_test, :database => :sqlite3) { }
|
57
|
+
(class << migration.adapter; self; end).included_modules.should include(SQL::Sqlite3)
|
58
|
+
end
|
57
59
|
end
|
58
|
-
end
|
59
60
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
if HAS_MYSQL
|
62
|
+
it "should extend with SQL::Mysql when adapter is MysqlAdapter" do
|
63
|
+
migration = DataMapper::Migration.new(1, :mysql_adapter_test, :database => :mysql) { }
|
64
|
+
(class << migration.adapter; self; end).included_modules.should include(SQL::Mysql)
|
65
|
+
end
|
65
66
|
end
|
66
|
-
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
68
|
+
if HAS_POSTGRES
|
69
|
+
it "should extend with SQL::Postgres when adapter is PostgresAdapter" do
|
70
|
+
migration = DataMapper::Migration.new(1, :postgres_adapter_test, :database => :postgres) { }
|
71
|
+
(class << migration.adapter; self; end).included_modules.should include(SQL::Postgresql)
|
72
|
+
end
|
73
73
|
end
|
74
74
|
end
|
75
|
-
end
|
76
75
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
76
|
+
describe DataMapper::Migration, 'defining actions' do
|
77
|
+
before do
|
78
|
+
@migration = DataMapper::Migration.new(1, :create_people_table, :verbose => false) { }
|
79
|
+
end
|
81
80
|
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
it "should have an #up method" do
|
82
|
+
@migration.should respond_to(:up)
|
83
|
+
end
|
85
84
|
|
86
|
-
|
87
|
-
|
88
|
-
|
85
|
+
it "should save the block passed into the #up method in @up_action" do
|
86
|
+
action = lambda {}
|
87
|
+
@migration.up(&action)
|
89
88
|
|
90
|
-
|
91
|
-
|
89
|
+
@migration.instance_variable_get(:@up_action).should == action
|
90
|
+
end
|
92
91
|
|
93
|
-
|
94
|
-
|
95
|
-
|
92
|
+
it "should have a #down method" do
|
93
|
+
@migration.should respond_to(:down)
|
94
|
+
end
|
96
95
|
|
97
|
-
|
98
|
-
|
99
|
-
|
96
|
+
it "should save the block passed into the #down method in @down_action" do
|
97
|
+
action = lambda {}
|
98
|
+
@migration.down(&action)
|
100
99
|
|
101
|
-
|
102
|
-
|
100
|
+
@migration.instance_variable_get(:@down_action).should == action
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should make available an #execute method" do
|
104
|
+
@migration.should respond_to(:execute)
|
105
|
+
end
|
103
106
|
|
104
|
-
|
105
|
-
|
107
|
+
it "should run the sql passed into the #execute method"
|
108
|
+
# TODO: Find out how to stub the DataMapper::database.execute method
|
106
109
|
end
|
107
110
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
+
describe DataMapper::Migration, "output" do
|
112
|
+
before do
|
113
|
+
@migration = DataMapper::Migration.new(1, :create_people_table) { }
|
114
|
+
@migration.stub!(:write) # so that we don't actually write anything to the console!
|
115
|
+
end
|
111
116
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
117
|
+
it "should #say a string with an indent" do
|
118
|
+
@migration.should_receive(:write).with(" Foobar")
|
119
|
+
@migration.say("Foobar", 2)
|
120
|
+
end
|
117
121
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
+
it "should #say with a default indent of 4" do
|
123
|
+
@migration.should_receive(:write).with(" Foobar")
|
124
|
+
@migration.say("Foobar")
|
125
|
+
end
|
122
126
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
127
|
+
it "should #say_with_time the running time of a block" do
|
128
|
+
@migration.should_receive(:write).with(/Block/)
|
129
|
+
@migration.should_receive(:write).with(/-> [\d]+/)
|
127
130
|
|
128
|
-
|
129
|
-
|
130
|
-
@migration.should_receive(:write).with(/-> [\d]+/)
|
131
|
+
@migration.say_with_time("Block"){ }
|
132
|
+
end
|
131
133
|
|
132
|
-
@migration.say_with_time("Block"){ }
|
133
134
|
end
|
134
|
-
|
135
135
|
end
|
136
136
|
end
|