migration_revert 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.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +66 -0
- data/Rakefile +2 -0
- data/lib/migration_revert.rb +6 -0
- data/lib/migration_revert/ext/command_recorder.rb +52 -0
- data/lib/migration_revert/ext/migration.rb +118 -0
- data/lib/migration_revert/version.rb +3 -0
- data/migration_revert.gemspec +18 -0
- metadata +72 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Marc-Andre Lafortune
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# MigrationRevert
|
2
|
+
|
3
|
+
Reversible migrations are very cool.
|
4
|
+
|
5
|
+
Some commands are not reversible, for example the removal of a table or a column.
|
6
|
+
|
7
|
+
Also, one downside is that it is now more difficult to write the reverse of a migration if `change` is used. When `up` and `down` were used, one could simply swap the code around.
|
8
|
+
|
9
|
+
These commits introduce `Migration#revert` that makes it trivial to revert a past migration, in part or in whole, or do the doing a reversible removal of a table/column.
|
10
|
+
|
11
|
+
Note that `revert` can even be called from legacy migrations using `up` & `down` and that it can revert legacy-style migrations too. For anyone changing their mind every second day, `revert` is fully nestable.
|
12
|
+
|
13
|
+
To have complete revertible capability, I would like to introduce a modified syntax for `change_column` that would allow it to be revertible; pull request upcoming when I get a chance...
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Reverses the migration commands for the given block and
|
18
|
+
the given migrations.
|
19
|
+
|
20
|
+
The following migration will remove the table 'horses'
|
21
|
+
and create the table 'apples' on the way up, and the reverse
|
22
|
+
on the way down.
|
23
|
+
|
24
|
+
This command can be nested.
|
25
|
+
|
26
|
+
class FixTLMigration < ActiveRecord::Migration
|
27
|
+
def change
|
28
|
+
revert do
|
29
|
+
create_table(:horses) do |t|
|
30
|
+
t.text :content
|
31
|
+
t.datetime :remind_at
|
32
|
+
end
|
33
|
+
end
|
34
|
+
create_table(:apples) do |t|
|
35
|
+
t.string :variety
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Or equivalently, if +TenderloveMigration+ is defined as in the
|
41
|
+
documentation for Migration:
|
42
|
+
|
43
|
+
class FixupTLMigration < ActiveRecord::Migration
|
44
|
+
def change
|
45
|
+
revert TenderloveMigration
|
46
|
+
|
47
|
+
create_table(:apples) do |t|
|
48
|
+
t.string :variety
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
## Installation
|
55
|
+
|
56
|
+
Add this line to your application's Gemfile:
|
57
|
+
|
58
|
+
gem 'migration_revert'
|
59
|
+
|
60
|
+
And then execute:
|
61
|
+
|
62
|
+
$ bundle
|
63
|
+
|
64
|
+
Or install it yourself as:
|
65
|
+
|
66
|
+
$ gem install migration_revert
|
data/Rakefile
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Migration
|
3
|
+
class CommandRecorder
|
4
|
+
def revert
|
5
|
+
@reverting = !@reverting
|
6
|
+
previous = @commands
|
7
|
+
@commands = []
|
8
|
+
yield
|
9
|
+
ensure
|
10
|
+
@commands = previous.concat(@commands.reverse)
|
11
|
+
@reverting = !@reverting
|
12
|
+
end
|
13
|
+
|
14
|
+
# record +command+. +command+ should be a method name and arguments.
|
15
|
+
# For example:
|
16
|
+
#
|
17
|
+
# recorder.record(:method_name, [:arg1, :arg2])
|
18
|
+
#
|
19
|
+
def record(*command, &block)
|
20
|
+
if @reverting
|
21
|
+
@commands << inverse_of(*command)
|
22
|
+
else
|
23
|
+
@commands << (command << block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
[:create_table, :create_join_table, :change_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default, :add_reference, :remove_reference].each do |method|
|
28
|
+
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
29
|
+
def #{method}(*args, &block) # def create_table(*args, &block)
|
30
|
+
record(:"#{method}", args, &block) # record(:create_table, args, &block)
|
31
|
+
end # end
|
32
|
+
EOV
|
33
|
+
end
|
34
|
+
alias :add_belongs_to :add_reference
|
35
|
+
alias :remove_belongs_to :remove_reference
|
36
|
+
|
37
|
+
# Returns the inverse of the given command
|
38
|
+
#
|
39
|
+
# recorder.inverse_of(:rename_table, [:old, :new])
|
40
|
+
# # => [:rename_table, [:new, :old]]
|
41
|
+
#
|
42
|
+
# This method will raise an +IrreversibleMigration+ exception if it cannot
|
43
|
+
# invert the +commands+.
|
44
|
+
#
|
45
|
+
def inverse_of(name, args)
|
46
|
+
method = :"invert_#{name}"
|
47
|
+
raise IrreversibleMigration unless respond_to?(method, true)
|
48
|
+
send(method, args)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
class Migration
|
3
|
+
# Reverses the migration commands for the given block and
|
4
|
+
# the given migrations.
|
5
|
+
#
|
6
|
+
# The following migration will remove the table 'horses'
|
7
|
+
# and create the table 'apples' on the way up, and the reverse
|
8
|
+
# on the way down.
|
9
|
+
#
|
10
|
+
# This command can be nested.
|
11
|
+
#
|
12
|
+
# class FixTLMigration < ActiveRecord::Migration
|
13
|
+
# def change
|
14
|
+
# revert do
|
15
|
+
# create_table(:horses) do |t|
|
16
|
+
# t.text :content
|
17
|
+
# t.datetime :remind_at
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
# create_table(:apples) do |t|
|
21
|
+
# t.string :variety
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Or equivalently, if +TenderloveMigration+ is defined as in the
|
27
|
+
# documentation for Migration:
|
28
|
+
#
|
29
|
+
# class FixupTLMigration < ActiveRecord::Migration
|
30
|
+
# def change
|
31
|
+
# revert TenderloveMigration
|
32
|
+
#
|
33
|
+
# create_table(:apples) do |t|
|
34
|
+
# t.string :variety
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
def revert(*migration_classes)
|
40
|
+
run(*migration_classes.reverse, :revert => true) unless migration_classes.empty?
|
41
|
+
if block_given?
|
42
|
+
if @connection.respond_to? :revert
|
43
|
+
@connection.revert { yield }
|
44
|
+
else
|
45
|
+
recorder = CommandRecorder.new(@connection)
|
46
|
+
@connection = recorder
|
47
|
+
suppress_messages do
|
48
|
+
@connection.revert { yield }
|
49
|
+
end
|
50
|
+
@connection = recorder.delegate
|
51
|
+
recorder.commands.each do |cmd, args, block|
|
52
|
+
send(cmd, *args, &block)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def reverting?
|
59
|
+
@connection.respond_to?(:reverting) && @connection.reverting
|
60
|
+
end
|
61
|
+
|
62
|
+
# Runs the given migration classes.
|
63
|
+
# Last argument can specify options:
|
64
|
+
# - :direction (default is :up)
|
65
|
+
# - :revert (default is false)
|
66
|
+
#
|
67
|
+
def run(*migration_classes)
|
68
|
+
opts = migration_classes.extract_options!
|
69
|
+
dir = opts[:direction] || :up
|
70
|
+
dir = (dir == :down ? :up : :down) if opts[:revert]
|
71
|
+
if reverting?
|
72
|
+
# If in revert and going :up, say, we want to execute :down without reverting, so
|
73
|
+
revert { run(*migration_classes, direction: dir, revert: true) }
|
74
|
+
else
|
75
|
+
migration_classes.each do |migration_class|
|
76
|
+
migration_class.new.exec_migration(@connection, dir)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# Execute this migration in the named direction
|
82
|
+
def migrate(direction)
|
83
|
+
return unless respond_to?(direction)
|
84
|
+
|
85
|
+
case direction
|
86
|
+
when :up then announce "migrating"
|
87
|
+
when :down then announce "reverting"
|
88
|
+
end
|
89
|
+
|
90
|
+
time = nil
|
91
|
+
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
92
|
+
time = Benchmark.measure do
|
93
|
+
exec_migration(conn, direction)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
case direction
|
98
|
+
when :up then announce "migrated (%.4fs)" % time.real; write
|
99
|
+
when :down then announce "reverted (%.4fs)" % time.real; write
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def exec_migration(conn, direction)
|
104
|
+
@connection = conn
|
105
|
+
if respond_to?(:change)
|
106
|
+
if direction == :down
|
107
|
+
revert { change }
|
108
|
+
else
|
109
|
+
change
|
110
|
+
end
|
111
|
+
else
|
112
|
+
send(direction)
|
113
|
+
end
|
114
|
+
ensure
|
115
|
+
@connection = nil
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/migration_revert/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Marc-Andre Lafortune"]
|
6
|
+
gem.email = ["github@marc-andre.ca"]
|
7
|
+
gem.description = %q{Revert ActiveRecord migrations}
|
8
|
+
gem.summary = %q{Revert ActiveRecord migrations in part or in whole}
|
9
|
+
gem.homepage = ""
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
13
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
14
|
+
gem.name = "migration_revert"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = MigrationRevert::VERSION
|
17
|
+
gem.add_runtime_dependency('activerecord', [">= 3.1.0"])
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: migration_revert
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Marc-Andre Lafortune
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-08-07 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activerecord
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.1.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.1.0
|
30
|
+
description: Revert ActiveRecord migrations
|
31
|
+
email:
|
32
|
+
- github@marc-andre.ca
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- lib/migration_revert.rb
|
43
|
+
- lib/migration_revert/ext/command_recorder.rb
|
44
|
+
- lib/migration_revert/ext/migration.rb
|
45
|
+
- lib/migration_revert/version.rb
|
46
|
+
- migration_revert.gemspec
|
47
|
+
homepage: ''
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ! '>='
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: '0'
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ! '>='
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 1.8.24
|
68
|
+
signing_key:
|
69
|
+
specification_version: 3
|
70
|
+
summary: Revert ActiveRecord migrations in part or in whole
|
71
|
+
test_files: []
|
72
|
+
has_rdoc:
|