1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
module ActiveRecord
class Migration
# ActiveRecord::Migration::CommandRecorder records commands done during
# a migration and knows how to reverse those commands. The CommandRecorder
# knows how to invert the following commands:
#
# * add_column
# * add_index
# * add_timestamp
# * create_table
# * remove_timestamps
# * rename_column
# * rename_index
# * rename_table
class CommandRecorder
attr_accessor :commands, :delegate
def initialize(delegate = nil)
@commands = []
@delegate = delegate
end
# record +command+. +command+ should be a method name and arguments.
# For example:
#
# recorder.record(:method_name, [:arg1, arg2])
def record(*command)
@commands << command
end
# Returns a list that represents commands that are the inverse of the
# commands stored in +commands+. For example:
#
# recorder.record(:rename_table, [:old, :new])
# recorder.inverse # => [:rename_table, [:new, :old]]
#
# This method will raise an IrreversibleMigration exception if it cannot
# invert the +commands+.
def inverse
@commands.reverse.map { |name, args|
method = :"invert_#{name}"
raise IrreversibleMigration unless respond_to?(method, true)
send(method, args)
}
end
def respond_to?(*args) # :nodoc:
super || delegate.respond_to?(*args)
end
[:create_table, :rename_table, :add_column, :remove_column, :rename_index, :rename_column, :add_index, :remove_index, :add_timestamps, :remove_timestamps, :change_column, :change_column_default].each do |method|
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{method}(*args)
record(:"#{method}", args)
end
EOV
end
private
def invert_create_table(args)
[:drop_table, args]
end
def invert_rename_table(args)
[:rename_table, args.reverse]
end
def invert_add_column(args)
[:remove_column, args.first(2)]
end
def invert_rename_index(args)
[:rename_index, args.reverse]
end
def invert_rename_column(args)
[:rename_column, [args.first] + args.last(2).reverse]
end
def invert_add_index(args)
table, columns, options = *args
if options && options[:name]
[:remove_index, [table, {:name => options[:name]}]]
else
[:remove_index, [table, {:column => columns}]]
end
end
def invert_remove_timestamps(args)
[:add_timestamps, args]
end
def invert_add_timestamps(args)
[:remove_timestamps, args]
end
# Forwards any missing method call to the \target.
def method_missing(method, *args, &block)
@delegate.send(method, *args, &block)
rescue NoMethodError => e
raise e, e.message.sub(/ for #<.*$/, " via proxy for #{@delegate}")
end
end
end
end
|