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.
Files changed (66) hide show
  1. data/.gitignore +4 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.markdown +189 -0
  4. data/Rakefile +2 -0
  5. data/bin/tern +6 -0
  6. data/lib/tern.rb +207 -0
  7. data/lib/tern/app.rb +65 -0
  8. data/lib/tern/generators/change.sql +4 -0
  9. data/lib/tern/generators/new/alterations/.empty_directory +0 -0
  10. data/lib/tern/generators/new/alterations/001_create_people.sql.example +16 -0
  11. data/lib/tern/generators/new/config.yml.tt +15 -0
  12. data/lib/tern/generators/new/definitions/sequence.yml +18 -0
  13. data/lib/tern/generators/new/definitions/ultimate_answer.sql.example +7 -0
  14. data/spec/projects/alterations/alterations/001_create_people.sql +8 -0
  15. data/spec/projects/alterations/alterations/002_create_animals.sql +8 -0
  16. data/spec/projects/alterations/alterations/003_create_plants.sql +8 -0
  17. data/spec/projects/alterations/config.yml +15 -0
  18. data/spec/projects/alterations/definitions/sequence.yml +18 -0
  19. data/spec/projects/alterations/definitions/ultimate_answer.sql.example +7 -0
  20. data/spec/projects/definitions/alterations/001_create_people.sql.example +16 -0
  21. data/spec/projects/definitions/config.yml +15 -0
  22. data/spec/projects/definitions/definitions/sequence.yml +19 -0
  23. data/spec/projects/definitions/definitions/ultimate_answer.sql +7 -0
  24. data/spec/projects/dependencies_1/alterations/001_create_widgets.sql +9 -0
  25. data/spec/projects/dependencies_1/config.yml +15 -0
  26. data/spec/projects/dependencies_1/definitions/sequence.yml +19 -0
  27. data/spec/projects/dependencies_1/definitions/ultimate_answer.sql.example +7 -0
  28. data/spec/projects/dependencies_1/definitions/widgets_view.sql +5 -0
  29. data/spec/projects/dependencies_2/alterations/001_create_widgets.sql +9 -0
  30. data/spec/projects/dependencies_2/config.yml +15 -0
  31. data/spec/projects/dependencies_2/definitions/sequence.yml +18 -0
  32. data/spec/projects/dependencies_2/definitions/ultimate_answer.sql.example +7 -0
  33. data/spec/projects/duplicated_alteration/alterations/001_create_people.sql +8 -0
  34. data/spec/projects/duplicated_alteration/alterations/002_create_animals.sql +8 -0
  35. data/spec/projects/duplicated_alteration/alterations/002_create_plants.sql +8 -0
  36. data/spec/projects/duplicated_alteration/config.yml +15 -0
  37. data/spec/projects/duplicated_alteration/definitions/sequence.yml +18 -0
  38. data/spec/projects/duplicated_alteration/definitions/ultimate_answer.sql.example +7 -0
  39. data/spec/projects/irreversible_alterations/alterations/001_create_people.sql +8 -0
  40. data/spec/projects/irreversible_alterations/alterations/002_create_animals.sql +4 -0
  41. data/spec/projects/irreversible_alterations/alterations/003_create_plants.sql +8 -0
  42. data/spec/projects/irreversible_alterations/config.yml +15 -0
  43. data/spec/projects/irreversible_alterations/definitions/sequence.yml +18 -0
  44. data/spec/projects/irreversible_alterations/definitions/ultimate_answer.sql.example +7 -0
  45. data/spec/projects/missing_alteration/alterations/001_create_people.sql +8 -0
  46. data/spec/projects/missing_alteration/alterations/003_create_plants.sql +8 -0
  47. data/spec/projects/missing_alteration/config.yml +15 -0
  48. data/spec/projects/missing_alteration/definitions/sequence.yml +18 -0
  49. data/spec/projects/missing_alteration/definitions/ultimate_answer.sql.example +7 -0
  50. data/spec/projects/multiple_sequences/alterations/001_create_people.sql.example +16 -0
  51. data/spec/projects/multiple_sequences/config.yml +15 -0
  52. data/spec/projects/multiple_sequences/definitions/create_view_a.sql +5 -0
  53. data/spec/projects/multiple_sequences/definitions/create_view_b.sql +5 -0
  54. data/spec/projects/multiple_sequences/definitions/sequence.yml +21 -0
  55. data/spec/projects/multiple_sequences/definitions/ultimate_answer.sql.example +7 -0
  56. data/spec/projects/new/alterations/001_create_people.sql.example +16 -0
  57. data/spec/projects/new/config.yml +15 -0
  58. data/spec/projects/new/definitions/sequence.yml +18 -0
  59. data/spec/projects/new/definitions/ultimate_answer.sql.example +7 -0
  60. data/spec/projects/no_alterations/alterations/001_create_people.sql.example +16 -0
  61. data/spec/projects/no_alterations/config.yml +15 -0
  62. data/spec/projects/no_alterations/definitions/sequence.yml +18 -0
  63. data/spec/projects/no_alterations/definitions/ultimate_answer.sql.example +7 -0
  64. data/spec/tern_spec.rb +215 -0
  65. data/tern.gemspec +23 -0
  66. metadata +174 -0
@@ -0,0 +1,18 @@
1
+ # Put the relative path to your definition files in the sequence they should run.
2
+ #
3
+ # Most definitions should go in the default list. These will be dropped and
4
+ # recreated every migration.
5
+ #
6
+ # Definitions that should not automatically be dropped and created can be put in
7
+ # different lists. This is useful for definitions that make take prohibitively
8
+ # to drop and create every migration such as check constraints.
9
+ #
10
+ # Run alternative sequences by specifying sequences in order to migrate command.
11
+ # Example: rake migrate sequences=expensive,default
12
+ #
13
+ # default:
14
+ # - ultimate_answer.sql
15
+ # expensive:
16
+ # - super_slow_check_constraint.sql
17
+
18
+ default: []
@@ -0,0 +1,7 @@
1
+ CREATE FUNCTION ultimate_answer() RETURNS integer AS $$
2
+ SELECT 42;
3
+ $$ LANGUAGE SQL;
4
+
5
+ ---- CREATE above / DROP below ----
6
+
7
+ DROP FUNCTION ultimate_answer();
@@ -0,0 +1,8 @@
1
+ CREATE TABLE people(
2
+ id serial PRIMARY KEY,
3
+ name varchar NOT NULL
4
+ );
5
+
6
+ ---- CREATE above / DROP below ----
7
+
8
+ DROP TABLE people;
@@ -0,0 +1,8 @@
1
+ CREATE TABLE plants(
2
+ id serial PRIMARY KEY,
3
+ name varchar NOT NULL
4
+ );
5
+
6
+ ---- CREATE above / DROP below ----
7
+
8
+ DROP TABLE plants;
@@ -0,0 +1,15 @@
1
+ alterations:
2
+ table: tern_alterations
3
+ column: version
4
+ definitions:
5
+ table: tern_definitions
6
+ environments:
7
+ development:
8
+ adapter: postgres
9
+ database: tern_test_development
10
+ test:
11
+ adapter: postgres
12
+ database: tern_test_test
13
+ production:
14
+ adapter: postgres
15
+ database: tern_test_production
@@ -0,0 +1,18 @@
1
+ # Put the relative path to your definition files in the sequence they should run.
2
+ #
3
+ # Most definitions should go in the default list. These will be dropped and
4
+ # recreated every migration.
5
+ #
6
+ # Definitions that should not automatically be dropped and created can be put in
7
+ # different lists. This is useful for definitions that make take prohibitively
8
+ # to drop and create every migration such as check constraints.
9
+ #
10
+ # Run alternative sequences by specifying sequences in order to migrate command.
11
+ # Example: rake migrate sequences=expensive,default
12
+ #
13
+ # default:
14
+ # - ultimate_answer.sql
15
+ # expensive:
16
+ # - super_slow_check_constraint.sql
17
+
18
+ default: []
@@ -0,0 +1,7 @@
1
+ CREATE FUNCTION ultimate_answer() RETURNS integer AS $$
2
+ SELECT 42;
3
+ $$ LANGUAGE SQL;
4
+
5
+ ---- CREATE above / DROP below ----
6
+
7
+ DROP FUNCTION ultimate_answer();
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ up do
3
+ run <<-END_SQL
4
+ CREATE TABLE people(
5
+ id serial PRIMARY KEY,
6
+ name varchar NOT NULL
7
+ );
8
+ END_SQL
9
+ end
10
+
11
+ down do
12
+ run <<-END_SQL
13
+ DROP TABLE people;
14
+ END_SQL
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ alterations:
2
+ table: tern_alterations
3
+ column: version
4
+ definitions:
5
+ table: tern_definitions
6
+ environments:
7
+ development:
8
+ adapter: postgres
9
+ database: tern_test_development
10
+ test:
11
+ adapter: postgres
12
+ database: tern_test_test
13
+ production:
14
+ adapter: postgres
15
+ database: tern_test_production
@@ -0,0 +1,5 @@
1
+ CREATE VIEW a AS SELECT 42;
2
+
3
+ ---- CREATE above / DROP below ----
4
+
5
+ DROP VIEW a;
@@ -0,0 +1,5 @@
1
+ CREATE VIEW b AS SELECT * FROM a;
2
+
3
+ ---- CREATE above / DROP below ----
4
+
5
+ DROP VIEW b;
@@ -0,0 +1,21 @@
1
+ # Put the relative path to your definition files in the sequence they should run.
2
+ #
3
+ # Most definitions should go in the default list. These will be dropped and
4
+ # recreated every migration.
5
+ #
6
+ # Definitions that should not automatically be dropped and created can be put in
7
+ # different lists. This is useful for definitions that make take prohibitively
8
+ # to drop and create every migration such as check constraints.
9
+ #
10
+ # Run alternative sequences by specifying sequences in order to migrate command.
11
+ # Example: rake migrate sequences=expensive,default
12
+ #
13
+ # default:
14
+ # - ultimate_answer.sql
15
+ # expensive:
16
+ # - super_slow_check_constraint.sql
17
+
18
+ default:
19
+ - create_view_b.sql
20
+ expensive:
21
+ - create_view_a.sql
@@ -0,0 +1,7 @@
1
+ CREATE FUNCTION ultimate_answer() RETURNS integer AS $$
2
+ SELECT 42;
3
+ $$ LANGUAGE SQL;
4
+
5
+ ---- CREATE above / DROP below ----
6
+
7
+ DROP FUNCTION ultimate_answer();
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ up do
3
+ run <<-END_SQL
4
+ CREATE TABLE people(
5
+ id serial PRIMARY KEY,
6
+ name varchar NOT NULL
7
+ );
8
+ END_SQL
9
+ end
10
+
11
+ down do
12
+ run <<-END_SQL
13
+ DROP TABLE people;
14
+ END_SQL
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ alterations:
2
+ table: tern_alterations
3
+ column: version
4
+ definitions:
5
+ table: tern_definitions
6
+ environments:
7
+ development:
8
+ adapter: postgres
9
+ database: tern_test_development
10
+ test:
11
+ adapter: postgres
12
+ database: tern_test_test
13
+ production:
14
+ adapter: postgres
15
+ database: tern_test_production
@@ -0,0 +1,18 @@
1
+ # Put the relative path to your definition files in the sequence they should run.
2
+ #
3
+ # Most definitions should go in the default list. These will be dropped and
4
+ # recreated every migration.
5
+ #
6
+ # Definitions that should not automatically be dropped and created can be put in
7
+ # different lists. This is useful for definitions that make take prohibitively
8
+ # to drop and create every migration such as check constraints.
9
+ #
10
+ # Run alternative sequences by specifying sequences in order to migrate command.
11
+ # Example: rake migrate sequences=expensive,default
12
+ #
13
+ # default:
14
+ # - ultimate_answer.sql
15
+ # expensive:
16
+ # - super_slow_check_constraint.sql
17
+
18
+ default: []
@@ -0,0 +1,7 @@
1
+ CREATE FUNCTION ultimate_answer() RETURNS integer AS $$
2
+ SELECT 42;
3
+ $$ LANGUAGE SQL;
4
+
5
+ ---- CREATE above / DROP below ----
6
+
7
+ DROP FUNCTION ultimate_answer();
@@ -0,0 +1,16 @@
1
+ Sequel.migration do
2
+ up do
3
+ run <<-END_SQL
4
+ CREATE TABLE people(
5
+ id serial PRIMARY KEY,
6
+ name varchar NOT NULL
7
+ );
8
+ END_SQL
9
+ end
10
+
11
+ down do
12
+ run <<-END_SQL
13
+ DROP TABLE people;
14
+ END_SQL
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ alterations:
2
+ table: tern_alterations
3
+ column: version
4
+ definitions:
5
+ table: tern_definitions
6
+ environments:
7
+ development:
8
+ adapter: postgres
9
+ database: tern_test_development
10
+ test:
11
+ adapter: postgres
12
+ database: tern_test_test
13
+ production:
14
+ adapter: postgres
15
+ database: tern_test_production
@@ -0,0 +1,18 @@
1
+ # Put the relative path to your definition files in the sequence they should run.
2
+ #
3
+ # Most definitions should go in the default list. These will be dropped and
4
+ # recreated every migration.
5
+ #
6
+ # Definitions that should not automatically be dropped and created can be put in
7
+ # different lists. This is useful for definitions that make take prohibitively
8
+ # to drop and create every migration such as check constraints.
9
+ #
10
+ # Run alternative sequences by specifying sequences in order to migrate command.
11
+ # Example: rake migrate sequences=expensive,default
12
+ #
13
+ # default:
14
+ # - ultimate_answer.sql
15
+ # expensive:
16
+ # - super_slow_check_constraint.sql
17
+
18
+ default: []
@@ -0,0 +1,7 @@
1
+ CREATE FUNCTION ultimate_answer() RETURNS integer AS $$
2
+ SELECT 42;
3
+ $$ LANGUAGE SQL;
4
+
5
+ ---- CREATE above / DROP below ----
6
+
7
+ DROP FUNCTION ultimate_answer();
@@ -0,0 +1,215 @@
1
+ require 'rspec'
2
+ require 'fileutils'
3
+ require 'sequel'
4
+ require 'tern'
5
+
6
+ $tmpdir = 'spec/tmp/' + Time.now.strftime("%Y-%m-%d-%H-%M-%S")
7
+ FileUtils.mkdir_p $tmpdir
8
+
9
+ RSpec.configure do |config|
10
+ def tern(args="")
11
+ lib = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
12
+ bin = File.expand_path(File.join(File.dirname(__FILE__), '..', 'bin', 'tern'))
13
+ `ruby -I #{lib} #{bin} #{args}`
14
+ end
15
+
16
+ def tern_migrate(path, args="")
17
+ Dir.chdir(path) do
18
+ tern "migrate #{args}"
19
+ end
20
+ end
21
+
22
+ def tern_generate(path, args="")
23
+ Dir.chdir(path) do
24
+ tern "generate #{args}"
25
+ end
26
+ end
27
+
28
+ def tmpdir
29
+ $tmpdir
30
+ end
31
+ end
32
+
33
+ describe "Change" do
34
+ describe "parse" do
35
+ it "splits on SPLIT_MARKER" do
36
+ ::Change.parse("create #{::Change::SPLIT_MARKER} drop").should == ["create ", " drop"]
37
+ end
38
+
39
+ it "returns entire string as create if no SPLIT_MARKER not found" do
40
+ ::Change.parse("create").should == ["create", nil]
41
+ end
42
+ end
43
+ end
44
+
45
+ describe "tern" do
46
+ it "displays task list when called without arguments" do
47
+ tern.should match(/Tasks/)
48
+ end
49
+
50
+ describe "new" do
51
+ it "creates a new project at path" do
52
+ Dir.chdir(tmpdir) do
53
+ tern "new tern_spec"
54
+ File.exist?("tern_spec").should be_true
55
+ File.exist?("tern_spec/alterations").should be_true
56
+ File.exist?("tern_spec/definitions").should be_true
57
+ File.exist?("tern_spec/config.yml").should be_true
58
+ end
59
+ end
60
+ end
61
+
62
+ describe "generate" do
63
+ before(:each) do |example|
64
+ @project_path = File.expand_path(File.join(tmpdir, example.object_id.to_s))
65
+ tern "new #{@project_path}"
66
+ end
67
+
68
+ it "prints an error message if config.yml is not found" do
69
+ tern_generate("spec/tmp", "alteration create_people").should match(/This directory does not appear to be a Tern project. config.yml not found./)
70
+ end
71
+
72
+ describe "alteration" do
73
+ it "creates alteration file with next available number prefixing name" do
74
+ tern_generate(@project_path, "alteration create_widgets")
75
+ File.exist?(File.join(@project_path, "alterations", "001_create_widgets.sql")).should be_true
76
+ tern_generate(@project_path, "alteration create_sprockets")
77
+ File.exist?(File.join(@project_path, "alterations", "002_create_sprockets.sql")).should be_true
78
+ end
79
+ end
80
+
81
+ describe "definition" do
82
+ it "creates definition file" do
83
+ tern_generate(@project_path, "definition create_a_view")
84
+ File.exist?(File.join(@project_path, "definitions", "create_a_view.sql")).should be_true
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "migrate" do
90
+ before(:each) do
91
+ `createdb tern_test_development`
92
+ @dev_db = Sequel.connect :adapter => :postgres, :database => 'tern_test_development'
93
+ end
94
+
95
+ after(:each) do
96
+ @dev_db.disconnect
97
+ `dropdb tern_test_development`
98
+ end
99
+
100
+ it "prints an error message if config.yml is not found" do
101
+ tern_migrate("spec/tmp").should match(/This directory does not appear to be a Tern project. config.yml not found./)
102
+ end
103
+
104
+ it "works without any alterations" do
105
+ tern_migrate("spec/projects/no_alterations").should be_empty
106
+ end
107
+
108
+ it "creates definitions table on first run" do
109
+ tern_migrate "spec/projects/new"
110
+ @dev_db.tables.should include(:tern_definitions)
111
+ end
112
+
113
+ it "creates alterations table on first run" do
114
+ tern_migrate "spec/projects/new"
115
+ @dev_db.tables.should include(:tern_alterations)
116
+ end
117
+
118
+ it "applies alterations" do
119
+ tern_migrate "spec/projects/alterations"
120
+ @dev_db.tables.should include(:people, :animals, :plants)
121
+ end
122
+
123
+ it "applies alterations to version n" do
124
+ tern_migrate "spec/projects/alterations", "-a 1"
125
+ @dev_db.tables.should include(:people)
126
+ tern_migrate "spec/projects/alterations", "-a 3"
127
+ @dev_db.tables.should include(:people, :animals, :plants)
128
+ end
129
+
130
+ it "reverts all alterations" do
131
+ tern_migrate "spec/projects/alterations"
132
+ tern_migrate "spec/projects/alterations", "-a 0"
133
+ @dev_db.tables.should_not include(:people, :animals, :plants)
134
+ end
135
+
136
+ it "reverts alterations to version n" do
137
+ tern_migrate "spec/projects/alterations"
138
+ tern_migrate "spec/projects/alterations", "-a 1"
139
+ @dev_db.tables.should include(:people)
140
+ @dev_db.tables.should_not include(:animals, :plants)
141
+ end
142
+
143
+ it "prints an error message when attempting to revert an irreversible alteration" do
144
+ tern_migrate "spec/projects/irreversible_alterations"
145
+ tern_migrate("spec/projects/irreversible_alterations", "-a 0").should match /Alteration 002 is irreversible/
146
+ end
147
+
148
+ it "prints an error message when missing alteration" do
149
+ tern_migrate("spec/projects/missing_alteration").should match /Alteration 002 is missing/
150
+ end
151
+
152
+ it "prints an error message when alteration version is duplicated" do
153
+ tern_migrate("spec/projects/duplicated_alteration").should match /Alteration 002 is duplicated/
154
+ end
155
+
156
+ it "uses environment parameter" do
157
+ `createdb tern_test_test`
158
+ test_db = Sequel.connect :adapter => :postgres, :database => 'tern_test_test'
159
+
160
+ tern_migrate "spec/projects/new", "-e test"
161
+ test_db.tables.should include(:tern_definitions)
162
+ @dev_db.tables.should_not include(:tern_definitions)
163
+
164
+ test_db.disconnect
165
+ `dropdb tern_test_test`
166
+ end
167
+
168
+ it "creates definitions" do
169
+ tern_migrate "spec/projects/definitions"
170
+ @dev_db.get{ultimate_answer{}}.should == 42
171
+ end
172
+
173
+ it "drops definitions" do
174
+ tern_migrate "spec/projects/definitions"
175
+ tern_migrate "spec/projects/new"
176
+ expect { @dev_db.get{ultimate_answer{}} }.to raise_error(Sequel::DatabaseError)
177
+ end
178
+
179
+ it "creates definitions after alterations" do
180
+ tern_migrate "spec/projects/dependencies_1"
181
+ @dev_db.tables.should include(:widgets)
182
+ expect { @dev_db[:widgets_view].all }.to_not raise_error(Sequel::DatabaseError)
183
+ end
184
+
185
+ it "drops existing definitions" do
186
+ tern_migrate "spec/projects/dependencies_1"
187
+ tern_migrate "spec/projects/dependencies_2"
188
+ expect { @dev_db[:widgets_view].all }.to raise_error(Sequel::DatabaseError)
189
+ end
190
+
191
+ context "multiple definition sequences" do
192
+ it "creates specified definition sequences in order" do
193
+ tern_migrate "spec/projects/multiple_sequences", "-d expensive default"
194
+ expect { @dev_db[:a].all }.to_not raise_error(Sequel::DatabaseError)
195
+ expect { @dev_db[:b].all }.to_not raise_error(Sequel::DatabaseError)
196
+ end
197
+
198
+ it "creates specified definition sequence" do
199
+ tern_migrate "spec/projects/multiple_sequences", "-d expensive"
200
+ expect { @dev_db[:a].all }.to_not raise_error(Sequel::DatabaseError)
201
+ end
202
+
203
+ it "ignores unspecified definition sequences" do
204
+ tern_migrate "spec/projects/multiple_sequences", "-d expensive"
205
+ expect { @dev_db[:b].all }.to raise_error(Sequel::DatabaseError)
206
+ end
207
+
208
+ it "removes specified definition sequence from database that does not exist in definitions" do
209
+ tern_migrate "spec/projects/multiple_sequences", "-d expensive"
210
+ tern_migrate "spec/projects/new", "-d expensive"
211
+ expect { @dev_db[:a].all }.to raise_error(Sequel::DatabaseError)
212
+ end
213
+ end
214
+ end
215
+ end