tern 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/MIT-LICENSE +20 -0
- data/README.markdown +189 -0
- data/Rakefile +2 -0
- data/bin/tern +6 -0
- data/lib/tern.rb +207 -0
- data/lib/tern/app.rb +65 -0
- data/lib/tern/generators/change.sql +4 -0
- data/lib/tern/generators/new/alterations/.empty_directory +0 -0
- data/lib/tern/generators/new/alterations/001_create_people.sql.example +16 -0
- data/lib/tern/generators/new/config.yml.tt +15 -0
- data/lib/tern/generators/new/definitions/sequence.yml +18 -0
- data/lib/tern/generators/new/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/alterations/alterations/001_create_people.sql +8 -0
- data/spec/projects/alterations/alterations/002_create_animals.sql +8 -0
- data/spec/projects/alterations/alterations/003_create_plants.sql +8 -0
- data/spec/projects/alterations/config.yml +15 -0
- data/spec/projects/alterations/definitions/sequence.yml +18 -0
- data/spec/projects/alterations/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/definitions/alterations/001_create_people.sql.example +16 -0
- data/spec/projects/definitions/config.yml +15 -0
- data/spec/projects/definitions/definitions/sequence.yml +19 -0
- data/spec/projects/definitions/definitions/ultimate_answer.sql +7 -0
- data/spec/projects/dependencies_1/alterations/001_create_widgets.sql +9 -0
- data/spec/projects/dependencies_1/config.yml +15 -0
- data/spec/projects/dependencies_1/definitions/sequence.yml +19 -0
- data/spec/projects/dependencies_1/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/dependencies_1/definitions/widgets_view.sql +5 -0
- data/spec/projects/dependencies_2/alterations/001_create_widgets.sql +9 -0
- data/spec/projects/dependencies_2/config.yml +15 -0
- data/spec/projects/dependencies_2/definitions/sequence.yml +18 -0
- data/spec/projects/dependencies_2/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/duplicated_alteration/alterations/001_create_people.sql +8 -0
- data/spec/projects/duplicated_alteration/alterations/002_create_animals.sql +8 -0
- data/spec/projects/duplicated_alteration/alterations/002_create_plants.sql +8 -0
- data/spec/projects/duplicated_alteration/config.yml +15 -0
- data/spec/projects/duplicated_alteration/definitions/sequence.yml +18 -0
- data/spec/projects/duplicated_alteration/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/irreversible_alterations/alterations/001_create_people.sql +8 -0
- data/spec/projects/irreversible_alterations/alterations/002_create_animals.sql +4 -0
- data/spec/projects/irreversible_alterations/alterations/003_create_plants.sql +8 -0
- data/spec/projects/irreversible_alterations/config.yml +15 -0
- data/spec/projects/irreversible_alterations/definitions/sequence.yml +18 -0
- data/spec/projects/irreversible_alterations/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/missing_alteration/alterations/001_create_people.sql +8 -0
- data/spec/projects/missing_alteration/alterations/003_create_plants.sql +8 -0
- data/spec/projects/missing_alteration/config.yml +15 -0
- data/spec/projects/missing_alteration/definitions/sequence.yml +18 -0
- data/spec/projects/missing_alteration/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/multiple_sequences/alterations/001_create_people.sql.example +16 -0
- data/spec/projects/multiple_sequences/config.yml +15 -0
- data/spec/projects/multiple_sequences/definitions/create_view_a.sql +5 -0
- data/spec/projects/multiple_sequences/definitions/create_view_b.sql +5 -0
- data/spec/projects/multiple_sequences/definitions/sequence.yml +21 -0
- data/spec/projects/multiple_sequences/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/new/alterations/001_create_people.sql.example +16 -0
- data/spec/projects/new/config.yml +15 -0
- data/spec/projects/new/definitions/sequence.yml +18 -0
- data/spec/projects/new/definitions/ultimate_answer.sql.example +7 -0
- data/spec/projects/no_alterations/alterations/001_create_people.sql.example +16 -0
- data/spec/projects/no_alterations/config.yml +15 -0
- data/spec/projects/no_alterations/definitions/sequence.yml +18 -0
- data/spec/projects/no_alterations/definitions/ultimate_answer.sql.example +7 -0
- data/spec/tern_spec.rb +215 -0
- data/tern.gemspec +23 -0
- metadata +174 -0
data/.gitignore
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Jack Christensen
|
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.markdown
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
Tern - The SQL Fan's Migrator
|
2
|
+
===============================
|
3
|
+
|
4
|
+
Tern is designed to simplify migrating database schemas with views, functions,
|
5
|
+
triggers, constraints and other objects where altering one may require dropping
|
6
|
+
and recreating others. For example, if view A selects from view B which selects
|
7
|
+
from table C, then altering C could require five steps: drop A, drop B, alter
|
8
|
+
C, recreate B, and recreate A. Tern can be told to alter C and it will
|
9
|
+
automatically perform the other four steps.
|
10
|
+
|
11
|
+
Installation
|
12
|
+
============
|
13
|
+
|
14
|
+
gem install tern
|
15
|
+
|
16
|
+
Requirements
|
17
|
+
============
|
18
|
+
|
19
|
+
Ruby 1.8.7+
|
20
|
+
|
21
|
+
Tern is built on top of [Sequel][1], so it can run on any database
|
22
|
+
[Sequel][1] supports. However, Tern relies on transactional DDL to keep the
|
23
|
+
database schema in a consistent state should something go wrong mid-migration.
|
24
|
+
[PostgreSQL][2] supports this. MySQL does not. Using Tern on a database
|
25
|
+
without transactional DDL is not recommended.
|
26
|
+
|
27
|
+
Creating a Tern Project
|
28
|
+
=======================
|
29
|
+
|
30
|
+
tern new my_project
|
31
|
+
|
32
|
+
Edit config.yml and set up your database environments. Database environment
|
33
|
+
parameters are passed directly to Sequel.connect so it uses the same
|
34
|
+
[options][3].
|
35
|
+
|
36
|
+
alterations:
|
37
|
+
table: tern_alterations
|
38
|
+
column: version
|
39
|
+
definitions:
|
40
|
+
table: tern_definitions
|
41
|
+
environments:
|
42
|
+
development:
|
43
|
+
adapter: postgres
|
44
|
+
database: my_project_development
|
45
|
+
test:
|
46
|
+
adapter: postgres
|
47
|
+
database: my_project_test
|
48
|
+
production:
|
49
|
+
adapter: postgres
|
50
|
+
database: my_project_production
|
51
|
+
|
52
|
+
Migration Types
|
53
|
+
===============
|
54
|
+
|
55
|
+
Tern divides potential database changes into alterations and definitions. An
|
56
|
+
alteration is something that cannot be reversed without potentially losing data.
|
57
|
+
For example, table creation is an alteration because dropping a table could
|
58
|
+
result in data loss. Alterations correspond directly with the migration style
|
59
|
+
popularized by Ruby on Rails. View creation is a definition, because it can be
|
60
|
+
dropped without possibility of data loss.
|
61
|
+
|
62
|
+
Both types of migrations have an extremely simple file format. They are simply
|
63
|
+
the create and drop SQL statements divided by a magic comment.
|
64
|
+
|
65
|
+
---- CREATE above / DROP below ----
|
66
|
+
|
67
|
+
Alterations
|
68
|
+
-----------
|
69
|
+
|
70
|
+
Alterations should be used to create, drop, and alter tables and any other
|
71
|
+
potentially irreversible migration.
|
72
|
+
|
73
|
+
tern generate alteration create_people
|
74
|
+
|
75
|
+
or abbreviate
|
76
|
+
|
77
|
+
tern g a create_people
|
78
|
+
|
79
|
+
This will create a numbered alteration in the alterations directory. Simply edit
|
80
|
+
the file and place the create code above the magic comment and the drop code
|
81
|
+
below it.
|
82
|
+
|
83
|
+
CREATE TABLE people(
|
84
|
+
id serial PRIMARY KEY,
|
85
|
+
name varchar NOT NULL
|
86
|
+
);
|
87
|
+
|
88
|
+
---- CREATE above / DROP below ----
|
89
|
+
|
90
|
+
DROP TABLE people;
|
91
|
+
|
92
|
+
If this alteration is irreversible such as a drop table, simply delete the magic
|
93
|
+
comment.
|
94
|
+
|
95
|
+
DROP TABLE widgets;
|
96
|
+
|
97
|
+
Definitions
|
98
|
+
-----------
|
99
|
+
|
100
|
+
Definitions should be used to specify the desired views, functions, triggers,
|
101
|
+
constraints, and other database objects that are reversible without possibility
|
102
|
+
of data loss.
|
103
|
+
|
104
|
+
tern generate definition create_ultimate_answer
|
105
|
+
|
106
|
+
or abbreviate
|
107
|
+
|
108
|
+
tern g d create_ultimate_answer
|
109
|
+
|
110
|
+
|
111
|
+
This will create the file create_ultimate_answer.sql in the definitions
|
112
|
+
directory. Add the create and drop commands around the magic comment.
|
113
|
+
|
114
|
+
CREATE FUNCTION ultimate_answer() RETURNS integer AS $$
|
115
|
+
SELECT 42;
|
116
|
+
$$ LANGUAGE SQL;
|
117
|
+
|
118
|
+
---- CREATE above / DROP below ----
|
119
|
+
|
120
|
+
DROP FUNCTION ultimate_answer();
|
121
|
+
|
122
|
+
Definitions need to be created in dropped in a particular order. This order is
|
123
|
+
defined in the file definitions/sequence.yml. Add this new definition to the
|
124
|
+
sequences file.
|
125
|
+
|
126
|
+
default:
|
127
|
+
- ultimate_answer.sql
|
128
|
+
|
129
|
+
Migrating
|
130
|
+
=========
|
131
|
+
|
132
|
+
tern migrate
|
133
|
+
|
134
|
+
To run alterations to a specific version:
|
135
|
+
|
136
|
+
tern migrate --alteration-version=42
|
137
|
+
|
138
|
+
To migrate a particular database environment:
|
139
|
+
|
140
|
+
tern migrate --environment=test
|
141
|
+
|
142
|
+
How it Works
|
143
|
+
============
|
144
|
+
|
145
|
+
Tern in migrates in three steps.
|
146
|
+
|
147
|
+
1. Drop all definitions in the reverse order they were created
|
148
|
+
2. Run alterations
|
149
|
+
3. Create all definitions
|
150
|
+
|
151
|
+
Tern stores the drop command for definitions in the database when it is first
|
152
|
+
created. This allows it to totally reverse all definitions even without the
|
153
|
+
original definition files. To make changes to the definitions just change the
|
154
|
+
files and rerun tern migrate. Tern will drop all definitions it has previously
|
155
|
+
created and create your new definitions.
|
156
|
+
|
157
|
+
Multiple Definition Sequences
|
158
|
+
=============================
|
159
|
+
|
160
|
+
Definitions such as a check constraint on a table with many rows may be too
|
161
|
+
time-consuming to drop and recreate for every migration. Tern allows you to
|
162
|
+
define multiple definition sequences in the sequence.yml file.
|
163
|
+
|
164
|
+
# default:
|
165
|
+
# - ultimate_answer.sql
|
166
|
+
# - my_first_view.sql
|
167
|
+
# - my_second_view.sql
|
168
|
+
# expensive:
|
169
|
+
# - super_slow_check_constraint.sql
|
170
|
+
|
171
|
+
Only the default sequence will normally run. To migrate the expensive
|
172
|
+
definition sequence use the --definition-sequences option. Note that default
|
173
|
+
will not run unless specified when using this option.
|
174
|
+
|
175
|
+
tern migrate --definition-sequences=expensive
|
176
|
+
|
177
|
+
Multiple sequences may be specified and they will be run in the order they are
|
178
|
+
listed.
|
179
|
+
|
180
|
+
tern migrate --definition-sequences=expensive default
|
181
|
+
|
182
|
+
License
|
183
|
+
=======
|
184
|
+
|
185
|
+
Copyright (c) 2011 Jack Christensen, released under the MIT license
|
186
|
+
|
187
|
+
[1]: http://sequel.rubyforge.org/
|
188
|
+
[2]: http://www.postgresql.org/
|
189
|
+
[3]: http://sequel.rubyforge.org/rdoc/files/doc/opening_databases_rdoc.html
|
data/Rakefile
ADDED
data/bin/tern
ADDED
data/lib/tern.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
class Change
|
5
|
+
SPLIT_MARKER = '---- CREATE above / DROP below ----'
|
6
|
+
|
7
|
+
def self.parse(string)
|
8
|
+
create_sql, drop_sql = string.split('---- CREATE above / DROP below ----')
|
9
|
+
[create_sql, drop_sql]
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class Definition < Change
|
14
|
+
class << self
|
15
|
+
attr_accessor :table_name
|
16
|
+
|
17
|
+
def table
|
18
|
+
DB[table_name]
|
19
|
+
end
|
20
|
+
|
21
|
+
def ensure_table_exists
|
22
|
+
DB.create_table? table_name do
|
23
|
+
primary_key :id
|
24
|
+
column :sequence, :text, :null => false
|
25
|
+
column :create_sql, :text, :null => false
|
26
|
+
column :drop_sql, :text, :null => false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def load_existing
|
31
|
+
table.order(:id).all.map do |row|
|
32
|
+
new row[:id], row[:sequence], row[:create_sql], row[:drop_sql]
|
33
|
+
end.group_by { |d| d.sequence }
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_targets(sequence_yml_path)
|
37
|
+
definition_sequences = YAML.load(File.read(sequence_yml_path))
|
38
|
+
sequence_dir = File.dirname(sequence_yml_path)
|
39
|
+
|
40
|
+
definition_sequences.keys.each do |sequence|
|
41
|
+
definition_sequences[sequence] = definition_sequences[sequence].map do |f|
|
42
|
+
create_sql, drop_sql = parse File.read(File.join(sequence_dir, f))
|
43
|
+
Definition.new nil, sequence, create_sql, drop_sql
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
definition_sequences
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
attr_reader :id
|
52
|
+
attr_reader :sequence
|
53
|
+
attr_reader :create_sql
|
54
|
+
attr_reader :drop_sql
|
55
|
+
|
56
|
+
def initialize(id, sequence, create_sql, drop_sql)
|
57
|
+
@id = id
|
58
|
+
@sequence = sequence
|
59
|
+
@create_sql = create_sql
|
60
|
+
@drop_sql = drop_sql
|
61
|
+
end
|
62
|
+
|
63
|
+
def create
|
64
|
+
DB.run create_sql
|
65
|
+
table.insert :sequence => sequence, :create_sql => create_sql, :drop_sql => drop_sql
|
66
|
+
end
|
67
|
+
|
68
|
+
def drop
|
69
|
+
DB.run drop_sql
|
70
|
+
table.filter(:id => id).delete
|
71
|
+
end
|
72
|
+
|
73
|
+
def table
|
74
|
+
self.class.table
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class Alteration < Change
|
79
|
+
class IrreversibleAlteration < StandardError
|
80
|
+
end
|
81
|
+
|
82
|
+
class MissingAlteration < StandardError
|
83
|
+
end
|
84
|
+
|
85
|
+
class DuplicateAlteration < StandardError
|
86
|
+
end
|
87
|
+
|
88
|
+
class << self
|
89
|
+
attr_accessor :table_name
|
90
|
+
attr_accessor :version_column
|
91
|
+
|
92
|
+
def table
|
93
|
+
DB[table_name]
|
94
|
+
end
|
95
|
+
|
96
|
+
def ensure_table_exists
|
97
|
+
vc = version_column # because create_table? block is run with different binding and can't access version_column
|
98
|
+
DB.create_table? table_name do
|
99
|
+
column vc, :integer, :null => false
|
100
|
+
end
|
101
|
+
table.insert version_column => 0
|
102
|
+
end
|
103
|
+
|
104
|
+
def version
|
105
|
+
table.get(version_column)
|
106
|
+
end
|
107
|
+
|
108
|
+
def version=(new_version)
|
109
|
+
table.update version_column => new_version
|
110
|
+
end
|
111
|
+
|
112
|
+
def load(alterations_path)
|
113
|
+
alterations = Dir.glob("#{alterations_path}/[0-9]*.sql").map do |path|
|
114
|
+
raise "This can't happen" unless File.basename(path) =~ /^(\d+)/
|
115
|
+
version = $1.to_i
|
116
|
+
create_sql, drop_sql = parse File.read(path)
|
117
|
+
new version, create_sql, drop_sql
|
118
|
+
end.sort_by(&:version)
|
119
|
+
|
120
|
+
alterations.each_with_index do |a, i|
|
121
|
+
expected = i+1
|
122
|
+
raise DuplicateAlteration, "Alteration #{a.version.to_s.rjust(3, "0")} is duplicated" if a.version < expected
|
123
|
+
raise MissingAlteration, "Alteration #{expected.to_s.rjust(3, "0")} is missing" if a.version > expected
|
124
|
+
end
|
125
|
+
|
126
|
+
alterations
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
attr_reader :version
|
131
|
+
attr_reader :create_sql
|
132
|
+
attr_reader :drop_sql
|
133
|
+
|
134
|
+
def initialize(version, create_sql, drop_sql)
|
135
|
+
@version = version
|
136
|
+
@create_sql = create_sql
|
137
|
+
@drop_sql = drop_sql
|
138
|
+
end
|
139
|
+
|
140
|
+
def create
|
141
|
+
DB.run create_sql
|
142
|
+
Alteration.version = version
|
143
|
+
end
|
144
|
+
|
145
|
+
def drop
|
146
|
+
raise IrreversibleAlteration, "Alteration #{version.to_s.rjust(3, "0")} is irreversible" unless drop_sql
|
147
|
+
DB.run drop_sql
|
148
|
+
Alteration.version = version - 1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Tern
|
153
|
+
def initialize(alterations_table, alterations_column, definitions_table)
|
154
|
+
Alteration.table_name = alterations_table.to_sym
|
155
|
+
Alteration.version_column = alterations_column.to_sym
|
156
|
+
Alteration.ensure_table_exists
|
157
|
+
@alterations = Alteration.load 'alterations'
|
158
|
+
|
159
|
+
Definition.table_name = definitions_table.to_sym
|
160
|
+
Definition.ensure_table_exists
|
161
|
+
@existing_definitions = Definition.load_existing
|
162
|
+
@target_definitions = Definition.load_targets 'definitions/sequence.yml'
|
163
|
+
end
|
164
|
+
|
165
|
+
def migrate(options={})
|
166
|
+
sequences = options[:sequences] || ['default']
|
167
|
+
DB.transaction do
|
168
|
+
drop_existing_definitions(sequences)
|
169
|
+
run_alterations(options[:version])
|
170
|
+
create_target_definitions(sequences)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
def run_alterations(version=nil)
|
176
|
+
return if @alterations.empty?
|
177
|
+
version ||= @alterations.size
|
178
|
+
|
179
|
+
if Alteration.version < version
|
180
|
+
@alterations[Alteration.version..version].each(&:create)
|
181
|
+
elsif
|
182
|
+
@alterations[version..(Alteration.version-1)].reverse.each(&:drop)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def drop_existing_definitions(sequences)
|
187
|
+
sequences.each do |s|
|
188
|
+
sequence = @existing_definitions[s]
|
189
|
+
if sequence
|
190
|
+
sequence.reverse.each do |definition|
|
191
|
+
definition.drop
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def create_target_definitions(sequences)
|
198
|
+
sequences.each do |s|
|
199
|
+
sequence = @target_definitions[s]
|
200
|
+
if sequence
|
201
|
+
sequence.each do |definition|
|
202
|
+
definition.create
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
data/lib/tern/app.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'thor/group'
|
3
|
+
|
4
|
+
class App < Thor
|
5
|
+
include Thor::Actions
|
6
|
+
|
7
|
+
source_root(File.join(File.dirname(__FILE__), "generators"))
|
8
|
+
|
9
|
+
attr_accessor :app_name
|
10
|
+
|
11
|
+
desc "new PATH", "Create a new Tern project"
|
12
|
+
def new(path)
|
13
|
+
self.app_name = File.basename path
|
14
|
+
self.destination_root = path
|
15
|
+
directory "new", "."
|
16
|
+
end
|
17
|
+
|
18
|
+
map "m" => "migrate"
|
19
|
+
desc "migrate", "Drop definitions, run alterations, then recreate definitions"
|
20
|
+
method_option :environment, :type => :string, :desc => "Database environment to load", :default => "development", :aliases => "-e"
|
21
|
+
method_option :alteration_version, :type => :numeric, :desc => "Target alteration version", :aliases => "-a"
|
22
|
+
method_option :definition_sequences, :type => :array, :desc => "Definition sequences to drop and create", :default => ["default"], :aliases => "-d"
|
23
|
+
def migrate
|
24
|
+
require 'yaml'
|
25
|
+
|
26
|
+
unless File.exist?('config.yml')
|
27
|
+
say 'This directory does not appear to be a Tern project. config.yml not found.'
|
28
|
+
return
|
29
|
+
end
|
30
|
+
config = YAML.load(File.read('config.yml'))
|
31
|
+
::Kernel.const_set("DB", Sequel.connect(config['environments'][options["environment"]])) # using const_set to avoid dynamic constant assignment error
|
32
|
+
|
33
|
+
begin
|
34
|
+
tern = Tern.new(config['alterations']['table'], config['alterations']['column'], config['definitions']['table'])
|
35
|
+
tern.migrate(:version => options["alteration_version"], :sequences => options["definition_sequences"])
|
36
|
+
rescue Alteration::IrreversibleAlteration, Alteration::MissingAlteration, Alteration::DuplicateAlteration
|
37
|
+
say $!, :red
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
map "g" => "generate"
|
42
|
+
desc "generate TYPE NAME", "Generate files"
|
43
|
+
def generate(type, name)
|
44
|
+
unless File.exist?('config.yml')
|
45
|
+
say 'This directory does not appear to be a Tern project. config.yml not found.', :red
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
case type
|
50
|
+
when 'a', 'alteration'
|
51
|
+
current_version = Dir.entries('alterations').map do |f|
|
52
|
+
f =~ /^(\d+)_.*.sql$/ ? $1.to_i : 0
|
53
|
+
end.max
|
54
|
+
zero_padded_next_version = (current_version+1).to_s.rjust(3, "0")
|
55
|
+
file_name = "#{zero_padded_next_version}_#{name}.sql"
|
56
|
+
copy_file "change.sql", "alterations/#{file_name}"
|
57
|
+
when 'd', 'definition'
|
58
|
+
file_name = "#{name}.sql"
|
59
|
+
copy_file "change.sql", "definitions/#{file_name}"
|
60
|
+
say "Remember to add #{file_name} in your sequence.yml file."
|
61
|
+
else
|
62
|
+
say "#{type} is not a valid TYPE", :red
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|