tern 0.6.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/.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
|