migrations 1.0.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.
- checksums.yaml +7 -0
- data/bin/migrate +319 -0
- data/lib/migrations/migration.rb +6 -0
- data/lib/migrations.rb +207 -0
- metadata +61 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 45ef69a67e679944490603eaa27ea10f1581e248
|
4
|
+
data.tar.gz: 4a7c63fe40651c10b01e2c4e5c978f6477b83084
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dad6e96c85c347d66b3ed58f62a144acc0be6437f89962ecf7ca218bf0ac1d24d407ae5c6e9f51219b1a001c8f6601f1508d3fc6a585018445b3c57312c0ab42
|
7
|
+
data.tar.gz: 8e7e2b8c33af112c4fa7902a193701c67dac37fc136b6ed08b0349ba5502606e18dd3d1b19b3798e9437feca1c37fa1f194dbd16d34d284a2e26f07ab4d44779
|
data/bin/migrate
ADDED
@@ -0,0 +1,319 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
require 'migrations'
|
4
|
+
|
5
|
+
class MigrationCommand
|
6
|
+
def initialize(args)
|
7
|
+
if args.empty?
|
8
|
+
usage
|
9
|
+
end
|
10
|
+
|
11
|
+
@prompt = true
|
12
|
+
|
13
|
+
if respond_to? args[0]
|
14
|
+
method = args.shift
|
15
|
+
|
16
|
+
if args[args.length-1] == '-y'
|
17
|
+
@prompt = false
|
18
|
+
args.pop
|
19
|
+
end
|
20
|
+
|
21
|
+
if args.empty?
|
22
|
+
send method
|
23
|
+
else
|
24
|
+
send method, *args
|
25
|
+
end
|
26
|
+
else
|
27
|
+
usage
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def usage
|
32
|
+
puts "\nMigrations for ruby by m4rkw - https://github.com/m4rkw/migration\n\n"
|
33
|
+
puts "Usage:\n\n"
|
34
|
+
|
35
|
+
format(
|
36
|
+
'migrate list' => 'list all migrations',
|
37
|
+
'migrate up [-y]' => 'run all pending migrations',
|
38
|
+
'migrate up <n> [-y]' => 'run <n> pending migrations',
|
39
|
+
'migrate down [-y]' => 'revert previous migration',
|
40
|
+
'migrate down <n> [-y]' => 'revert previous <n> migrations',
|
41
|
+
'migrate down all [-y]' => 'revert all previous migrations',
|
42
|
+
'migrate to <version> [-y]' => 'migrate up or down to a specific version',
|
43
|
+
'migrate add <name> [-y]' => 'generate a new migration class',
|
44
|
+
'migrate set <version || "none"> [-y]' => 'set the migrated version without running any migrations',
|
45
|
+
'migrate apply <version> [-y]' => 'apply a specific migration without updating the version',
|
46
|
+
'migrate revert <version> [-y]' => 'revert a specific migration without updating the version'
|
47
|
+
)
|
48
|
+
puts "\n"
|
49
|
+
exit
|
50
|
+
end
|
51
|
+
|
52
|
+
def format(keys)
|
53
|
+
width = (keys.keys.max do |a,b| a.length <=> b.length end).length + 2
|
54
|
+
|
55
|
+
keys.each do |key, value|
|
56
|
+
puts "#{key.ljust(width,' ')}: #{value}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def format_table(list)
|
61
|
+
widths = {}
|
62
|
+
total = 0
|
63
|
+
|
64
|
+
list[0].keys.each do |key|
|
65
|
+
widths[key] = key.length
|
66
|
+
list.each do |item|
|
67
|
+
if table_value(item[key]).length > widths[key]
|
68
|
+
widths[key] = table_value(item[key]).length
|
69
|
+
end
|
70
|
+
end
|
71
|
+
total += widths[key]
|
72
|
+
end
|
73
|
+
|
74
|
+
table_spacer(widths)
|
75
|
+
|
76
|
+
list[0].keys.each do |key|
|
77
|
+
print "| #{key.to_s.rjust(widths[key],' ')} "
|
78
|
+
end
|
79
|
+
|
80
|
+
puts "|"
|
81
|
+
|
82
|
+
table_spacer(widths)
|
83
|
+
|
84
|
+
list.each do |item|
|
85
|
+
item.each do |key,value|
|
86
|
+
print "| #{table_value(value).rjust(widths[key],' ')} "
|
87
|
+
end
|
88
|
+
puts "|"
|
89
|
+
end
|
90
|
+
|
91
|
+
table_spacer(widths)
|
92
|
+
end
|
93
|
+
|
94
|
+
def table_spacer(widths)
|
95
|
+
widths.each do |key, width|
|
96
|
+
print "+-" + ("-" * width) + "-"
|
97
|
+
end
|
98
|
+
puts "+"
|
99
|
+
end
|
100
|
+
|
101
|
+
def table_value(value)
|
102
|
+
if !!value == value
|
103
|
+
return value ? 'yes' : 'no'
|
104
|
+
end
|
105
|
+
if value.is_a?(Time)
|
106
|
+
return value.strftime('%Y-%m-%d %H:%M:%S')
|
107
|
+
end
|
108
|
+
|
109
|
+
value
|
110
|
+
end
|
111
|
+
|
112
|
+
def list
|
113
|
+
m = Migrations.new
|
114
|
+
|
115
|
+
list = m.list
|
116
|
+
|
117
|
+
if list.empty?
|
118
|
+
puts "\nNo migrations found.\n\n"
|
119
|
+
else
|
120
|
+
puts "\n"
|
121
|
+
format_table(list)
|
122
|
+
puts "\n"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def prompt_continue
|
127
|
+
print "\nContinue? [y/N] "
|
128
|
+
input = STDIN.gets.strip
|
129
|
+
|
130
|
+
if input[0].downcase != 'y'
|
131
|
+
puts "Aborting.\n"
|
132
|
+
exit
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def list_migrations(title, versions)
|
137
|
+
puts "\n#{title}\n\n"
|
138
|
+
|
139
|
+
versions.each do |version|
|
140
|
+
puts version
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def up(n=nil)
|
145
|
+
m = Migrations.new
|
146
|
+
|
147
|
+
pending = m.pending(n)
|
148
|
+
|
149
|
+
if pending.empty?
|
150
|
+
puts "\nNo migrations pending.\n\n"
|
151
|
+
else
|
152
|
+
if @prompt
|
153
|
+
list_migrations "Migrations pending:",pending
|
154
|
+
prompt_continue
|
155
|
+
end
|
156
|
+
|
157
|
+
puts "\n"
|
158
|
+
m.up(n)
|
159
|
+
puts "\n"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def down(n=nil)
|
164
|
+
if n.nil?
|
165
|
+
n = 1
|
166
|
+
elsif n == 'all'
|
167
|
+
n = nil
|
168
|
+
end
|
169
|
+
|
170
|
+
m = Migrations.new
|
171
|
+
|
172
|
+
applied = m.applied(n)
|
173
|
+
|
174
|
+
if applied.empty?
|
175
|
+
puts "\nNo migrations applied.\n\n"
|
176
|
+
else
|
177
|
+
if @prompt
|
178
|
+
list_migrations "Migrations to roll back:",applied
|
179
|
+
prompt_continue
|
180
|
+
end
|
181
|
+
|
182
|
+
puts "\n"
|
183
|
+
m.down(n)
|
184
|
+
puts "\n"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def add(name)
|
189
|
+
name = name.gsub(/[^a-zA-Z0-9_]+/,'')
|
190
|
+
|
191
|
+
m = Migrations.new
|
192
|
+
|
193
|
+
puts "\n"
|
194
|
+
m.generate(name)
|
195
|
+
puts "\n"
|
196
|
+
end
|
197
|
+
|
198
|
+
def to(version)
|
199
|
+
m = Migrations.new
|
200
|
+
|
201
|
+
pending = m.pending
|
202
|
+
|
203
|
+
if pending.include? version
|
204
|
+
to_apply = []
|
205
|
+
|
206
|
+
pending.each do |pending_version|
|
207
|
+
to_apply.push pending_version
|
208
|
+
|
209
|
+
if pending_version == version
|
210
|
+
break
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
if to_apply.empty?
|
215
|
+
puts "\nNothing to do, we are already at that version.\n\n"
|
216
|
+
return
|
217
|
+
end
|
218
|
+
|
219
|
+
if @prompt
|
220
|
+
list_migrations "Migrations to apply:", to_apply
|
221
|
+
prompt_continue
|
222
|
+
end
|
223
|
+
|
224
|
+
puts "\n"
|
225
|
+
to_apply.each do |version|
|
226
|
+
m.apply version
|
227
|
+
end
|
228
|
+
puts "\n"
|
229
|
+
return
|
230
|
+
|
231
|
+
else
|
232
|
+
applied = m.applied
|
233
|
+
|
234
|
+
if applied.include? version
|
235
|
+
if applied[0] == version
|
236
|
+
puts "\nNothing to do, we are already at that version.\n\n"
|
237
|
+
return
|
238
|
+
end
|
239
|
+
to_revert = []
|
240
|
+
applied.each do |applied_version|
|
241
|
+
if applied_version == version
|
242
|
+
break
|
243
|
+
end
|
244
|
+
to_revert.push applied_version
|
245
|
+
end
|
246
|
+
|
247
|
+
if @prompt
|
248
|
+
list_migrations "Migrations to roll back:", to_revert
|
249
|
+
prompt_continue
|
250
|
+
end
|
251
|
+
|
252
|
+
puts "\n"
|
253
|
+
to_revert.each do |version|
|
254
|
+
m.revert version
|
255
|
+
end
|
256
|
+
puts "\n"
|
257
|
+
return
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
puts "\nVersion not found.\n\n"
|
262
|
+
end
|
263
|
+
|
264
|
+
def set(version)
|
265
|
+
m = Migrations.new
|
266
|
+
|
267
|
+
pending = m.pending
|
268
|
+
|
269
|
+
if pending.include? version
|
270
|
+
pending.each do |pending_version|
|
271
|
+
m.insert pending_version
|
272
|
+
|
273
|
+
if pending_version == version
|
274
|
+
break
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
puts "\nVersion has been set to: #{version}\n\n"
|
279
|
+
return
|
280
|
+
else
|
281
|
+
applied = m.applied
|
282
|
+
|
283
|
+
if applied.first == version
|
284
|
+
puts "\nNothing to do, we are already at version: #{version}\n\n"
|
285
|
+
return
|
286
|
+
end
|
287
|
+
|
288
|
+
if (applied.include?(version) || version == "none")
|
289
|
+
applied.each do |applied_version|
|
290
|
+
if applied_version == version
|
291
|
+
break
|
292
|
+
end
|
293
|
+
m.remove applied_version
|
294
|
+
end
|
295
|
+
|
296
|
+
if version == "none"
|
297
|
+
puts "\nVersion has been set to (none)\n\n"
|
298
|
+
else
|
299
|
+
puts "\nVersion has been set to: #{version}\n\n"
|
300
|
+
end
|
301
|
+
return
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
puts "\nVersion #{version} was not found.\n\n"
|
306
|
+
end
|
307
|
+
|
308
|
+
def apply(version)
|
309
|
+
m = Migrations.new
|
310
|
+
m.apply(version,false)
|
311
|
+
end
|
312
|
+
|
313
|
+
def revert(version)
|
314
|
+
m = Migrations.new
|
315
|
+
m.revert(version,false)
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
MigrationCommand.new(ARGV)
|
data/lib/migrations.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
|
2
|
+
require 'fileutils'
|
3
|
+
require 'sequel'
|
4
|
+
|
5
|
+
class Migrations
|
6
|
+
attr_accessor :path
|
7
|
+
attr_accessor :verbose
|
8
|
+
|
9
|
+
def initialize(db=nil)
|
10
|
+
config_file = "config/config.rb"
|
11
|
+
|
12
|
+
if db
|
13
|
+
@db = db
|
14
|
+
elsif File.exist? config_file
|
15
|
+
config = {}
|
16
|
+
instance_eval(File.read(config_file)).each do |key, value|
|
17
|
+
config[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
@db = Sequel.connect("#{config[:db][:type]}://#{config[:db][:user]}:#{config[:db][:pass]}@#{config[:db][:host]}/#{config[:db][:name]}")
|
21
|
+
else
|
22
|
+
raise "#{config_file} was not found and no database handle was passed to us."
|
23
|
+
end
|
24
|
+
|
25
|
+
@path = 'migrations'
|
26
|
+
@verbose = true
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_migrations
|
30
|
+
ensure_migration_table
|
31
|
+
|
32
|
+
filesystem = []
|
33
|
+
database = {}
|
34
|
+
all = []
|
35
|
+
|
36
|
+
Dir.glob(@path + '/*.rb').map do |migration|
|
37
|
+
class_name = migration.match(/(M[0-9]{8}_[0-9]{6}_[a-zA-Z0-9_]+)\.rb\z/)[1]
|
38
|
+
|
39
|
+
filesystem.push class_name
|
40
|
+
all.push class_name
|
41
|
+
end
|
42
|
+
|
43
|
+
@db[:migration].each do |migration|
|
44
|
+
database[migration[:version]] = migration[:applied_at]
|
45
|
+
if !all.include? migration[:version]
|
46
|
+
all.push migration
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
[filesystem,database,all]
|
51
|
+
end
|
52
|
+
|
53
|
+
def list
|
54
|
+
filesystem, database, all = get_migrations
|
55
|
+
|
56
|
+
list = []
|
57
|
+
|
58
|
+
all.sort.each do |migration|
|
59
|
+
list.push({
|
60
|
+
:version => migration,
|
61
|
+
:applied => database[migration] ? true : false,
|
62
|
+
:applied_at => database[migration] ? database[migration] : ''
|
63
|
+
})
|
64
|
+
end
|
65
|
+
|
66
|
+
list
|
67
|
+
end
|
68
|
+
|
69
|
+
def pending(n=nil)
|
70
|
+
filesystem, database, all = get_migrations
|
71
|
+
|
72
|
+
pending = []
|
73
|
+
|
74
|
+
filesystem.sort.each do |migration|
|
75
|
+
if !database.keys.include?(migration) and (n.nil? or pending.length < n)
|
76
|
+
pending.push migration
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
pending
|
81
|
+
end
|
82
|
+
|
83
|
+
def applied(n=nil)
|
84
|
+
filesystem, database, all = get_migrations
|
85
|
+
|
86
|
+
applied = []
|
87
|
+
|
88
|
+
database.keys.sort.reverse.each do |migration|
|
89
|
+
if (n.nil? or applied.length < n)
|
90
|
+
applied.push migration
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
applied
|
95
|
+
end
|
96
|
+
|
97
|
+
def generate(name)
|
98
|
+
if !File.exist? @path
|
99
|
+
FileUtils.mkdir_p @path
|
100
|
+
end
|
101
|
+
|
102
|
+
t = Time.now
|
103
|
+
|
104
|
+
while 1
|
105
|
+
t = Time.at(t.to_i + 1)
|
106
|
+
class_name = 'M' + t.strftime('%Y%m%d_%H%M%S') + '_' + name.gsub(/[^a-zA-Z0-9_]/i, '')
|
107
|
+
migration = "#{@path}/#{class_name}.rb"
|
108
|
+
|
109
|
+
if !File.exist? migration
|
110
|
+
break
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
File.open(migration,'w') do |f|
|
115
|
+
f.write("
|
116
|
+
require 'migrations/migration'
|
117
|
+
|
118
|
+
class #{class_name} < Migration
|
119
|
+
def up
|
120
|
+
end
|
121
|
+
|
122
|
+
def down
|
123
|
+
end
|
124
|
+
end
|
125
|
+
")
|
126
|
+
@verbose and puts "Create new migration: #{migration}"
|
127
|
+
|
128
|
+
true
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def ensure_migration_table
|
133
|
+
begin
|
134
|
+
table = @db[:migration].map(:version)
|
135
|
+
rescue Sequel::DatabaseError => e
|
136
|
+
if e.message.match /Table .*? doesn\'t exist/
|
137
|
+
@db.create_table :migration do
|
138
|
+
primary_key :id
|
139
|
+
String :version, :unique => true, :null => false
|
140
|
+
DateTime :applied_at
|
141
|
+
end
|
142
|
+
else
|
143
|
+
raise Sequel::DatabaseError e
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def up(n=nil)
|
149
|
+
ensure_migration_table
|
150
|
+
|
151
|
+
pending(n).each do |version|
|
152
|
+
apply(version)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def down(n=nil)
|
157
|
+
ensure_migration_table
|
158
|
+
|
159
|
+
applied(n).each do |version|
|
160
|
+
revert(version)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def apply(version, update_table=true)
|
165
|
+
migration = @path + '/' + version + '.rb'
|
166
|
+
|
167
|
+
load migration
|
168
|
+
|
169
|
+
m = Object::const_get(version).new(@db)
|
170
|
+
|
171
|
+
@verbose and print "Applying: #{version} ... "
|
172
|
+
|
173
|
+
m.up
|
174
|
+
|
175
|
+
@verbose and puts "ok"
|
176
|
+
|
177
|
+
update_table and insert(version)
|
178
|
+
end
|
179
|
+
|
180
|
+
def insert(version)
|
181
|
+
@db[:migration].insert(:version => version, :applied_at => Time.now)
|
182
|
+
end
|
183
|
+
|
184
|
+
def revert(version, update_table=true)
|
185
|
+
migration = @path + '/' + version + '.rb'
|
186
|
+
|
187
|
+
if !File.exist? migration
|
188
|
+
raise "Migration file #{migration} not found."
|
189
|
+
end
|
190
|
+
|
191
|
+
load migration
|
192
|
+
|
193
|
+
m = Object::const_get(version).new(@db)
|
194
|
+
|
195
|
+
@verbose and print "Rolling back: #{version} ... "
|
196
|
+
|
197
|
+
m.down
|
198
|
+
|
199
|
+
@verbose and puts "ok"
|
200
|
+
|
201
|
+
update_table and remove(version)
|
202
|
+
end
|
203
|
+
|
204
|
+
def remove(version)
|
205
|
+
@db[:migration].where('version = ?',version).delete
|
206
|
+
end
|
207
|
+
end
|
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: migrations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- m4rkw
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.25'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.25'
|
27
|
+
description: Ruby database migration library
|
28
|
+
email: m@rkw.io
|
29
|
+
executables:
|
30
|
+
- migrate
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- bin/migrate
|
35
|
+
- lib/migrations.rb
|
36
|
+
- lib/migrations/migration.rb
|
37
|
+
homepage: https://github.com/m4rkw/migrations
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
metadata: {}
|
41
|
+
post_install_message:
|
42
|
+
rdoc_options: []
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
requirements: []
|
56
|
+
rubyforge_project:
|
57
|
+
rubygems_version: 2.4.5
|
58
|
+
signing_key:
|
59
|
+
specification_version: 4
|
60
|
+
summary: Migrations
|
61
|
+
test_files: []
|