Merge pull request #8267 from marcandre/reversible_drop_table_etc
Reversible commands
This commit is contained in:
commit
68e91da765
@ -1,5 +1,22 @@
|
||||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary.
|
||||
|
||||
* The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given.
|
||||
The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible).
|
||||
The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default`
|
||||
|
||||
* New method `reversible` makes it possible to specify code to be run when migrating up or down.
|
||||
See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method)
|
||||
|
||||
* New method `revert` will revert a whole migration or the given block.
|
||||
If migrating down, the given migration / block is run normally.
|
||||
See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations)
|
||||
|
||||
Attempting to revert the methods `execute`, `remove_columns` and `change_column` will now raise an IrreversibleMigration instead of actually executing them without any output.
|
||||
|
||||
*Marc-André Lafortune*
|
||||
|
||||
* Serialized attributes can be serialized in integer columns.
|
||||
Fix #8575.
|
||||
|
||||
|
@ -423,7 +423,7 @@ def change_default(column_name, default)
|
||||
# t.remove(:qualification)
|
||||
# t.remove(:qualification, :experience)
|
||||
def remove(*column_names)
|
||||
@base.remove_column(@table_name, *column_names)
|
||||
@base.remove_columns(@table_name, *column_names)
|
||||
end
|
||||
|
||||
# Removes the given index from the table.
|
||||
@ -490,20 +490,8 @@ def remove_references(*args)
|
||||
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
||||
def #{column_type}(*args) # def string(*args)
|
||||
options = args.extract_options! # options = args.extract_options!
|
||||
column_names = args # column_names = args
|
||||
type = :'#{column_type}' # type = :string
|
||||
column_names.each do |name| # column_names.each do |name|
|
||||
column = ColumnDefinition.new(@base, name.to_s, type) # column = ColumnDefinition.new(@base, name, type)
|
||||
if options[:limit] # if options[:limit]
|
||||
column.limit = options[:limit] # column.limit = options[:limit]
|
||||
elsif native[type].is_a?(Hash) # elsif native[type].is_a?(Hash)
|
||||
column.limit = native[type][:limit] # column.limit = native[type][:limit]
|
||||
end # end
|
||||
column.precision = options[:precision] # column.precision = options[:precision]
|
||||
column.scale = options[:scale] # column.scale = options[:scale]
|
||||
column.default = options[:default] # column.default = options[:default]
|
||||
column.null = options[:null] # column.null = options[:null]
|
||||
@base.add_column(@table_name, name, column.sql_type, options) # @base.add_column(@table_name, name, column.sql_type, options)
|
||||
args.each do |name| # column_names.each do |name|
|
||||
@base.add_column(@table_name, name, :#{column_type}, options) # @base.add_column(@table_name, name, :string, options)
|
||||
end # end
|
||||
end # end
|
||||
EOV
|
||||
|
@ -214,6 +214,17 @@ def create_join_table(table_1, table_2, options = {})
|
||||
end
|
||||
end
|
||||
|
||||
# Drops the join table specified by the given arguments.
|
||||
# See create_join_table for details.
|
||||
#
|
||||
# Although this command ignores the block if one is given, it can be helpful
|
||||
# to provide one in a migration's +change+ method so it can be reverted.
|
||||
# In that case, the block will be used by create_join_table.
|
||||
def drop_join_table(table_1, table_2, options = {})
|
||||
join_table_name = find_join_table_name(table_1, table_2, options)
|
||||
drop_table(join_table_name)
|
||||
end
|
||||
|
||||
# A block for changing columns in +table+.
|
||||
#
|
||||
# # change_table() yields a Table instance
|
||||
@ -294,6 +305,10 @@ def rename_table(table_name, new_name)
|
||||
end
|
||||
|
||||
# Drops a table from the database.
|
||||
#
|
||||
# Although this command ignores +options+ and the block if one is given, it can be helpful
|
||||
# to provide these in a migration's +change+ method so it can be reverted.
|
||||
# In that case, +options+ and the block will be used by create_table.
|
||||
def drop_table(table_name, options = {})
|
||||
execute "DROP TABLE #{quote_table_name(table_name)}"
|
||||
end
|
||||
@ -306,14 +321,26 @@ def add_column(table_name, column_name, type, options = {})
|
||||
execute(add_column_sql)
|
||||
end
|
||||
|
||||
# Removes the column(s) from the table definition.
|
||||
# Removes the given columns from the table definition.
|
||||
#
|
||||
# remove_columns(:suppliers, :qualification, :experience)
|
||||
def remove_columns(table_name, *column_names)
|
||||
raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
|
||||
column_names.each do |column_name|
|
||||
remove_column(table_name, column_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Removes the column from the table definition.
|
||||
#
|
||||
# remove_column(:suppliers, :qualification)
|
||||
# remove_columns(:suppliers, :qualification, :experience)
|
||||
def remove_column(table_name, *column_names)
|
||||
columns_for_remove(table_name, *column_names).each {|column_name| execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{column_name}" }
|
||||
#
|
||||
# The +type+ and +options+ parameters will be ignored if present. It can be helpful
|
||||
# to provide these in a migration's +change+ method so it can be reverted.
|
||||
# In that case, +type+ and +options+ will be used by add_column.
|
||||
def remove_column(table_name, column_name, type = nil, options = {})
|
||||
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name{column_name}}"
|
||||
end
|
||||
alias :remove_columns :remove_column
|
||||
|
||||
# Changes the column's definition according to the new options.
|
||||
# See TableDefinition#column for details of the options you can use.
|
||||
@ -662,7 +689,8 @@ def index_name_for_remove(table_name, options = {})
|
||||
end
|
||||
|
||||
def columns_for_remove(table_name, *column_names)
|
||||
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.blank?
|
||||
ActiveSupport::Deprecation.warn("columns_for_remove is deprecated and will be removed in the future")
|
||||
raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.blank?
|
||||
column_names.map {|column_name| quote_column_name(column_name) }
|
||||
end
|
||||
|
||||
|
@ -444,15 +444,11 @@ def add_column(table_name, column_name, type, options = {}) #:nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
def remove_column(table_name, *column_names) #:nodoc:
|
||||
raise ArgumentError.new("You must specify at least one column name. Example: remove_column(:people, :first_name)") if column_names.empty?
|
||||
column_names.each do |column_name|
|
||||
alter_table(table_name) do |definition|
|
||||
definition.columns.delete(definition[column_name])
|
||||
end
|
||||
def remove_column(table_name, column_name, type = nil, options = {}) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
definition.columns.delete(definition[column_name])
|
||||
end
|
||||
end
|
||||
alias :remove_columns :remove_column
|
||||
|
||||
def change_column_default(table_name, column_name, default) #:nodoc:
|
||||
alter_table(table_name) do |definition|
|
||||
|
@ -373,22 +373,129 @@ def initialize(name = self.class.name, version = nil)
|
||||
@name = name
|
||||
@version = version
|
||||
@connection = nil
|
||||
@reverting = false
|
||||
end
|
||||
|
||||
# instantiate the delegate object after initialize is defined
|
||||
self.verbose = true
|
||||
self.delegate = new
|
||||
|
||||
def revert
|
||||
@reverting = true
|
||||
yield
|
||||
ensure
|
||||
@reverting = false
|
||||
# Reverses the migration commands for the given block and
|
||||
# the given migrations.
|
||||
#
|
||||
# The following migration will remove the table 'horses'
|
||||
# and create the table 'apples' on the way up, and the reverse
|
||||
# on the way down.
|
||||
#
|
||||
# class FixTLMigration < ActiveRecord::Migration
|
||||
# def change
|
||||
# revert do
|
||||
# create_table(:horses) do |t|
|
||||
# t.text :content
|
||||
# t.datetime :remind_at
|
||||
# end
|
||||
# end
|
||||
# create_table(:apples) do |t|
|
||||
# t.string :variety
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Or equivalently, if +TenderloveMigration+ is defined as in the
|
||||
# documentation for Migration:
|
||||
#
|
||||
# require_relative '2012121212_tenderlove_migration'
|
||||
#
|
||||
# class FixupTLMigration < ActiveRecord::Migration
|
||||
# def change
|
||||
# revert TenderloveMigration
|
||||
#
|
||||
# create_table(:apples) do |t|
|
||||
# t.string :variety
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# This command can be nested.
|
||||
def revert(*migration_classes)
|
||||
run(*migration_classes.reverse, revert: true) unless migration_classes.empty?
|
||||
if block_given?
|
||||
if @connection.respond_to? :revert
|
||||
@connection.revert { yield }
|
||||
else
|
||||
recorder = CommandRecorder.new(@connection)
|
||||
@connection = recorder
|
||||
suppress_messages do
|
||||
@connection.revert { yield }
|
||||
end
|
||||
@connection = recorder.delegate
|
||||
recorder.commands.each do |cmd, args, block|
|
||||
send(cmd, *args, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def reverting?
|
||||
@reverting
|
||||
@connection.respond_to?(:reverting) && @connection.reverting
|
||||
end
|
||||
|
||||
class ReversibleBlockHelper < Struct.new(:reverting)
|
||||
def up
|
||||
yield unless reverting
|
||||
end
|
||||
|
||||
def down
|
||||
yield if reverting
|
||||
end
|
||||
end
|
||||
|
||||
# Used to specify an operation that can be run in one direction or another.
|
||||
# Call the methods +up+ and +down+ of the yielded object to run a block
|
||||
# only in one given direction.
|
||||
# The whole block will be called in the right order within the migration.
|
||||
#
|
||||
# In the following example, the looping on users will always be done
|
||||
# when the three columns 'first_name', 'last_name' and 'full_name' exist,
|
||||
# even when migrating down:
|
||||
#
|
||||
# class SplitNameMigration < ActiveRecord::Migration
|
||||
# def change
|
||||
# add_column :users, :first_name, :string
|
||||
# add_column :users, :last_name, :string
|
||||
#
|
||||
# reversible do |dir|
|
||||
# User.reset_column_information
|
||||
# User.all.each do |u|
|
||||
# dir.up { u.first_name, u.last_name = u.full_name.split(' ') }
|
||||
# dir.down { u.full_name = "#{u.first_name} #{u.last_name}" }
|
||||
# u.save
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# revert { add_column :users, :full_name, :string }
|
||||
# end
|
||||
# end
|
||||
def reversible
|
||||
helper = ReversibleBlockHelper.new(reverting?)
|
||||
transaction{ yield helper }
|
||||
end
|
||||
|
||||
# Runs the given migration classes.
|
||||
# Last argument can specify options:
|
||||
# - :direction (default is :up)
|
||||
# - :revert (default is false)
|
||||
def run(*migration_classes)
|
||||
opts = migration_classes.extract_options!
|
||||
dir = opts[:direction] || :up
|
||||
dir = (dir == :down ? :up : :down) if opts[:revert]
|
||||
if reverting?
|
||||
# If in revert and going :up, say, we want to execute :down without reverting, so
|
||||
revert { run(*migration_classes, direction: dir, revert: true) }
|
||||
else
|
||||
migration_classes.each do |migration_class|
|
||||
migration_class.new.exec_migration(@connection, dir)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def up
|
||||
@ -414,29 +521,9 @@ def migrate(direction)
|
||||
|
||||
time = nil
|
||||
ActiveRecord::Base.connection_pool.with_connection do |conn|
|
||||
@connection = conn
|
||||
if respond_to?(:change)
|
||||
if direction == :down
|
||||
recorder = CommandRecorder.new(@connection)
|
||||
suppress_messages do
|
||||
@connection = recorder
|
||||
change
|
||||
end
|
||||
@connection = conn
|
||||
time = Benchmark.measure {
|
||||
self.revert {
|
||||
recorder.inverse.each do |cmd, args|
|
||||
send(cmd, *args)
|
||||
end
|
||||
}
|
||||
}
|
||||
else
|
||||
time = Benchmark.measure { change }
|
||||
end
|
||||
else
|
||||
time = Benchmark.measure { send(direction) }
|
||||
time = Benchmark.measure do
|
||||
exec_migration(conn, direction)
|
||||
end
|
||||
@connection = nil
|
||||
end
|
||||
|
||||
case direction
|
||||
@ -445,6 +532,21 @@ def migrate(direction)
|
||||
end
|
||||
end
|
||||
|
||||
def exec_migration(conn, direction)
|
||||
@connection = conn
|
||||
if respond_to?(:change)
|
||||
if direction == :down
|
||||
revert { change }
|
||||
else
|
||||
change
|
||||
end
|
||||
else
|
||||
send(direction)
|
||||
end
|
||||
ensure
|
||||
@connection = nil
|
||||
end
|
||||
|
||||
def write(text="")
|
||||
puts(text) if verbose
|
||||
end
|
||||
@ -483,7 +585,7 @@ def method_missing(method, *arguments, &block)
|
||||
arg_list = arguments.map{ |a| a.inspect } * ', '
|
||||
|
||||
say_with_time "#{method}(#{arg_list})" do
|
||||
unless reverting?
|
||||
unless @connection.respond_to? :revert
|
||||
unless arguments.empty? || method == :execute
|
||||
arguments[0] = Migrator.proper_table_name(arguments.first)
|
||||
arguments[1] = Migrator.proper_table_name(arguments.second) if method == :rename_table
|
||||
|
@ -16,69 +16,116 @@ class Migration
|
||||
class CommandRecorder
|
||||
include JoinTable
|
||||
|
||||
attr_accessor :commands, :delegate
|
||||
attr_accessor :commands, :delegate, :reverting
|
||||
|
||||
def initialize(delegate = nil)
|
||||
@commands = []
|
||||
@delegate = delegate
|
||||
@reverting = false
|
||||
end
|
||||
|
||||
# While executing the given block, the recorded will be in reverting mode.
|
||||
# All commands recorded will end up being recorded reverted
|
||||
# and in reverse order.
|
||||
# For example:
|
||||
#
|
||||
# recorder.revert{ recorder.record(:rename_table, [:old, :new]) }
|
||||
# # same effect as recorder.record(:rename_table, [:new, :old])
|
||||
def revert
|
||||
@reverting = !@reverting
|
||||
previous = @commands
|
||||
@commands = []
|
||||
yield
|
||||
ensure
|
||||
@commands = previous.concat(@commands.reverse)
|
||||
@reverting = !@reverting
|
||||
end
|
||||
|
||||
# record +command+. +command+ should be a method name and arguments.
|
||||
# For example:
|
||||
#
|
||||
# recorder.record(:method_name, [:arg1, :arg2])
|
||||
def record(*command)
|
||||
@commands << command
|
||||
def record(*command, &block)
|
||||
if @reverting
|
||||
@commands << inverse_of(*command, &block)
|
||||
else
|
||||
@commands << (command << block)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a list that represents commands that are the inverse of the
|
||||
# commands stored in +commands+. For example:
|
||||
# Returns the inverse of the given command. For example:
|
||||
#
|
||||
# recorder.record(:rename_table, [:old, :new])
|
||||
# recorder.inverse # => [:rename_table, [:new, :old]]
|
||||
# recorder.inverse_of(:rename_table, [:old, :new])
|
||||
# # => [: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)
|
||||
}
|
||||
# invert the +command+.
|
||||
def inverse_of(command, args, &block)
|
||||
method = :"invert_#{command}"
|
||||
raise IrreversibleMigration unless respond_to?(method, true)
|
||||
send(method, args, &block)
|
||||
end
|
||||
|
||||
def respond_to?(*args) # :nodoc:
|
||||
super || delegate.respond_to?(*args)
|
||||
end
|
||||
|
||||
[: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|
|
||||
[:create_table, :create_join_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, :transaction,
|
||||
:drop_join_table, :drop_table, :remove_index,
|
||||
:change_column, :execute, :remove_columns, # irreversible methods need to be here too
|
||||
].each do |method|
|
||||
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
||||
def #{method}(*args) # def create_table(*args)
|
||||
record(:"#{method}", args) # record(:create_table, args)
|
||||
end # end
|
||||
def #{method}(*args, &block) # def create_table(*args, &block)
|
||||
record(:"#{method}", args, &block) # record(:create_table, args, &block)
|
||||
end # end
|
||||
EOV
|
||||
end
|
||||
alias :add_belongs_to :add_reference
|
||||
alias :remove_belongs_to :remove_reference
|
||||
|
||||
private
|
||||
|
||||
def invert_create_table(args)
|
||||
[:drop_table, [args.first]]
|
||||
def change_table(table_name, options = {})
|
||||
yield ConnectionAdapters::Table.new(table_name, self)
|
||||
end
|
||||
|
||||
def invert_create_join_table(args)
|
||||
table_name = find_join_table_name(*args)
|
||||
private
|
||||
|
||||
[:drop_table, [table_name]]
|
||||
module StraightReversions
|
||||
private
|
||||
{ transaction: :transaction,
|
||||
create_table: :drop_table,
|
||||
create_join_table: :drop_join_table,
|
||||
add_column: :remove_column,
|
||||
add_timestamps: :remove_timestamps,
|
||||
add_reference: :remove_reference,
|
||||
}.each do |cmd, inv|
|
||||
[[inv, cmd], [cmd, inv]].each do |method, inverse|
|
||||
class_eval <<-EOV, __FILE__, __LINE__ + 1
|
||||
def invert_#{method}(args, &block) # def invert_create_table(args, &block)
|
||||
[:#{inverse}, args, block] # [:drop_table, args, block]
|
||||
end # end
|
||||
EOV
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
include StraightReversions
|
||||
|
||||
def invert_drop_table(args, &block)
|
||||
if args.size == 1 && block == nil
|
||||
raise ActiveRecord::IrreversibleMigration, "To avoid mistakes, drop_table is only reversible if given options or a block (can be empty)."
|
||||
end
|
||||
super
|
||||
end
|
||||
|
||||
def invert_rename_table(args)
|
||||
[:rename_table, args.reverse]
|
||||
end
|
||||
|
||||
def invert_add_column(args)
|
||||
[:remove_column, args.first(2)]
|
||||
def invert_remove_column(args)
|
||||
raise ActiveRecord::IrreversibleMigration, "remove_column is only reversible if given a type." if args.size <= 2
|
||||
super
|
||||
end
|
||||
|
||||
def invert_rename_index(args)
|
||||
@ -91,27 +138,18 @@ def invert_rename_column(args)
|
||||
|
||||
def invert_add_index(args)
|
||||
table, columns, options = *args
|
||||
index_name = options.try(:[], :name)
|
||||
options_hash = index_name ? {:name => index_name} : {:column => columns}
|
||||
[:remove_index, [table, options_hash]]
|
||||
[:remove_index, [table, (options || {}).merge(column: columns)]]
|
||||
end
|
||||
|
||||
def invert_remove_timestamps(args)
|
||||
[:add_timestamps, args]
|
||||
def invert_remove_index(args)
|
||||
table, options = *args
|
||||
raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option." unless options && options[:column]
|
||||
|
||||
options = options.dup
|
||||
[:add_index, [table, options.delete(:column), options]]
|
||||
end
|
||||
|
||||
def invert_add_timestamps(args)
|
||||
[:remove_timestamps, args]
|
||||
end
|
||||
|
||||
def invert_add_reference(args)
|
||||
[:remove_reference, args]
|
||||
end
|
||||
alias :invert_add_belongs_to :invert_add_reference
|
||||
|
||||
def invert_remove_reference(args)
|
||||
[:add_reference, args]
|
||||
end
|
||||
alias :invert_remove_belongs_to :invert_remove_reference
|
||||
|
||||
# Forwards any missing method call to the \target.
|
||||
|
@ -21,28 +21,16 @@ def change
|
||||
end
|
||||
end
|
||||
<%- else -%>
|
||||
def up
|
||||
def change
|
||||
<% attributes.each do |attribute| -%>
|
||||
<%- if migration_action -%>
|
||||
<%- if attribute.reference? -%>
|
||||
remove_reference :<%= table_name %>, :<%= attribute.name %><%= ', polymorphic: true' if attribute.polymorphic? %>
|
||||
remove_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
|
||||
<%- else -%>
|
||||
remove_column :<%= table_name %>, :<%= attribute.name %>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
end
|
||||
|
||||
def down
|
||||
<% attributes.reverse.each do |attribute| -%>
|
||||
<%- if migration_action -%>
|
||||
<%- if attribute.reference? -%>
|
||||
add_reference :<%= table_name %>, :<%= attribute.name %><%= attribute.inject_options %>
|
||||
<%- else -%>
|
||||
add_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
|
||||
<%- if attribute.has_index? -%>
|
||||
add_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
|
||||
remove_index :<%= table_name %>, :<%= attribute.index_name %><%= attribute.inject_index_options %>
|
||||
<%- end -%>
|
||||
remove_column :<%= table_name %>, :<%= attribute.name %>, :<%= attribute.type %><%= attribute.inject_options %>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
<%- end -%>
|
||||
|
@ -17,6 +17,37 @@ def change
|
||||
end
|
||||
end
|
||||
|
||||
class InvertibleRevertMigration < SilentMigration
|
||||
def change
|
||||
revert do
|
||||
create_table("horses") do |t|
|
||||
t.column :content, :text
|
||||
t.column :remind_at, :datetime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class InvertibleByPartsMigration < SilentMigration
|
||||
attr_writer :test
|
||||
def change
|
||||
create_table("new_horses") do |t|
|
||||
t.column :breed, :string
|
||||
end
|
||||
reversible do |dir|
|
||||
@test.yield :both
|
||||
dir.up { @test.yield :up }
|
||||
dir.down { @test.yield :down }
|
||||
end
|
||||
revert do
|
||||
create_table("horses") do |t|
|
||||
t.column :content, :text
|
||||
t.column :remind_at, :datetime
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class NonInvertibleMigration < SilentMigration
|
||||
def change
|
||||
create_table("horses") do |t|
|
||||
@ -40,6 +71,23 @@ def self.down
|
||||
end
|
||||
end
|
||||
|
||||
class RevertWholeMigration < SilentMigration
|
||||
def initialize(name = self.class.name, version = nil, migration)
|
||||
@migration = migration
|
||||
super(name, version)
|
||||
end
|
||||
|
||||
def change
|
||||
revert @migration
|
||||
end
|
||||
end
|
||||
|
||||
class NestedRevertWholeMigration < RevertWholeMigration
|
||||
def change
|
||||
revert { super }
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
if ActiveRecord::Base.connection.table_exists?("horses")
|
||||
ActiveRecord::Base.connection.drop_table("horses")
|
||||
@ -67,6 +115,83 @@ def test_migrate_down
|
||||
assert !migration.connection.table_exists?("horses")
|
||||
end
|
||||
|
||||
def test_migrate_revert
|
||||
migration = InvertibleMigration.new
|
||||
revert = InvertibleRevertMigration.new
|
||||
migration.migrate :up
|
||||
revert.migrate :up
|
||||
assert !migration.connection.table_exists?("horses")
|
||||
revert.migrate :down
|
||||
assert migration.connection.table_exists?("horses")
|
||||
migration.migrate :down
|
||||
assert !migration.connection.table_exists?("horses")
|
||||
end
|
||||
|
||||
def test_migrate_revert_by_part
|
||||
InvertibleMigration.new.migrate :up
|
||||
received = []
|
||||
migration = InvertibleByPartsMigration.new
|
||||
migration.test = ->(dir){
|
||||
assert migration.connection.table_exists?("horses")
|
||||
assert migration.connection.table_exists?("new_horses")
|
||||
received << dir
|
||||
}
|
||||
migration.migrate :up
|
||||
assert_equal [:both, :up], received
|
||||
assert !migration.connection.table_exists?("horses")
|
||||
assert migration.connection.table_exists?("new_horses")
|
||||
migration.migrate :down
|
||||
assert_equal [:both, :up, :both, :down], received
|
||||
assert migration.connection.table_exists?("horses")
|
||||
assert !migration.connection.table_exists?("new_horses")
|
||||
end
|
||||
|
||||
def test_migrate_revert_whole_migration
|
||||
migration = InvertibleMigration.new
|
||||
[LegacyMigration, InvertibleMigration].each do |klass|
|
||||
revert = RevertWholeMigration.new(klass)
|
||||
migration.migrate :up
|
||||
revert.migrate :up
|
||||
assert !migration.connection.table_exists?("horses")
|
||||
revert.migrate :down
|
||||
assert migration.connection.table_exists?("horses")
|
||||
migration.migrate :down
|
||||
assert !migration.connection.table_exists?("horses")
|
||||
end
|
||||
end
|
||||
|
||||
def test_migrate_nested_revert_whole_migration
|
||||
revert = NestedRevertWholeMigration.new(InvertibleRevertMigration)
|
||||
revert.migrate :down
|
||||
assert revert.connection.table_exists?("horses")
|
||||
revert.migrate :up
|
||||
assert !revert.connection.table_exists?("horses")
|
||||
end
|
||||
|
||||
def test_revert_order
|
||||
block = Proc.new{|t| t.string :name }
|
||||
recorder = ActiveRecord::Migration::CommandRecorder.new(ActiveRecord::Base.connection)
|
||||
recorder.instance_eval do
|
||||
create_table("apples", &block)
|
||||
revert do
|
||||
create_table("bananas", &block)
|
||||
revert do
|
||||
create_table("clementines")
|
||||
create_table("dates")
|
||||
end
|
||||
create_table("elderberries")
|
||||
end
|
||||
revert do
|
||||
create_table("figs")
|
||||
create_table("grapes")
|
||||
end
|
||||
end
|
||||
assert_equal [[:create_table, ["apples"], block], [:drop_table, ["elderberries"], nil],
|
||||
[:create_table, ["clementines"], nil], [:create_table, ["dates"], nil],
|
||||
[:drop_table, ["bananas"], block], [:drop_table, ["grapes"], nil],
|
||||
[:drop_table, ["figs"], nil]], recorder.commands
|
||||
end
|
||||
|
||||
def test_legacy_up
|
||||
LegacyMigration.migrate :up
|
||||
assert ActiveRecord::Base.connection.table_exists?("horses"), "horses should exist"
|
||||
|
@ -3,21 +3,8 @@
|
||||
module ActiveRecord
|
||||
class Migration
|
||||
class TableTest < ActiveRecord::TestCase
|
||||
class MockConnection < MiniTest::Mock
|
||||
def native_database_types
|
||||
{
|
||||
:string => 'varchar(255)',
|
||||
:integer => 'integer',
|
||||
}
|
||||
end
|
||||
|
||||
def type_to_sql(type, limit, precision, scale)
|
||||
native_database_types[type]
|
||||
end
|
||||
end
|
||||
|
||||
def setup
|
||||
@connection = MockConnection.new
|
||||
@connection = MiniTest::Mock.new
|
||||
end
|
||||
|
||||
def teardown
|
||||
@ -98,26 +85,18 @@ def test_remove_timestamps_creates_updated_at_and_created_at
|
||||
end
|
||||
end
|
||||
|
||||
def string_column
|
||||
@connection.native_database_types[:string]
|
||||
end
|
||||
|
||||
def integer_column
|
||||
@connection.native_database_types[:integer]
|
||||
end
|
||||
|
||||
def test_integer_creates_integer_column
|
||||
with_change_table do |t|
|
||||
@connection.expect :add_column, nil, [:delete_me, :foo, integer_column, {}]
|
||||
@connection.expect :add_column, nil, [:delete_me, :bar, integer_column, {}]
|
||||
@connection.expect :add_column, nil, [:delete_me, :foo, :integer, {}]
|
||||
@connection.expect :add_column, nil, [:delete_me, :bar, :integer, {}]
|
||||
t.integer :foo, :bar
|
||||
end
|
||||
end
|
||||
|
||||
def test_string_creates_string_column
|
||||
with_change_table do |t|
|
||||
@connection.expect :add_column, nil, [:delete_me, :foo, string_column, {}]
|
||||
@connection.expect :add_column, nil, [:delete_me, :bar, string_column, {}]
|
||||
@connection.expect :add_column, nil, [:delete_me, :foo, :string, {}]
|
||||
@connection.expect :add_column, nil, [:delete_me, :bar, :string, {}]
|
||||
t.string :foo, :bar
|
||||
end
|
||||
end
|
||||
@ -194,14 +173,14 @@ def test_change_default_changes_column
|
||||
|
||||
def test_remove_drops_single_column
|
||||
with_change_table do |t|
|
||||
@connection.expect :remove_column, nil, [:delete_me, :bar]
|
||||
@connection.expect :remove_columns, nil, [:delete_me, :bar]
|
||||
t.remove :bar
|
||||
end
|
||||
end
|
||||
|
||||
def test_remove_drops_multiple_columns
|
||||
with_change_table do |t|
|
||||
@connection.expect :remove_column, nil, [:delete_me, :bar, :baz]
|
||||
@connection.expect :remove_columns, nil, [:delete_me, :bar, :baz]
|
||||
t.remove :bar, :baz
|
||||
end
|
||||
end
|
||||
|
@ -26,7 +26,7 @@ def create_table(name); end
|
||||
}.new)
|
||||
assert recorder.respond_to?(:create_table), 'respond_to? create_table'
|
||||
recorder.send(:create_table, :horses)
|
||||
assert_equal [[:create_table, [:horses]]], recorder.commands
|
||||
assert_equal [[:create_table, [:horses], nil]], recorder.commands
|
||||
end
|
||||
|
||||
def test_unknown_commands_delegate
|
||||
@ -34,10 +34,15 @@ def test_unknown_commands_delegate
|
||||
assert_equal 'bar', recorder.foo
|
||||
end
|
||||
|
||||
def test_unknown_commands_raise_exception_if_they_cannot_delegate
|
||||
@recorder.record :execute, ['some sql']
|
||||
def test_inverse_of_raise_exception_on_unknown_commands
|
||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||
@recorder.inverse
|
||||
@recorder.inverse_of :execute, ['some sql']
|
||||
end
|
||||
end
|
||||
|
||||
def test_irreversible_commands_raise_exception
|
||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||
@recorder.revert{ @recorder.execute 'some sql' }
|
||||
end
|
||||
end
|
||||
|
||||
@ -46,121 +51,196 @@ def test_record
|
||||
assert_equal 1, @recorder.commands.length
|
||||
end
|
||||
|
||||
def test_inverse
|
||||
@recorder.record :create_table, [:system_settings]
|
||||
assert_equal 1, @recorder.inverse.length
|
||||
|
||||
@recorder.record :rename_table, [:old, :new]
|
||||
assert_equal 2, @recorder.inverse.length
|
||||
end
|
||||
|
||||
def test_inverted_commands_are_reveresed
|
||||
@recorder.record :create_table, [:hello]
|
||||
@recorder.record :create_table, [:world]
|
||||
tables = @recorder.inverse.map(&:last)
|
||||
def test_inverted_commands_are_reversed
|
||||
@recorder.revert do
|
||||
@recorder.record :create_table, [:hello]
|
||||
@recorder.record :create_table, [:world]
|
||||
end
|
||||
tables = @recorder.commands.map{|_cmd, args, _block| args}
|
||||
assert_equal [[:world], [:hello]], tables
|
||||
end
|
||||
|
||||
def test_invert_create_table
|
||||
@recorder.record :create_table, [:system_settings]
|
||||
drop_table = @recorder.inverse.first
|
||||
assert_equal [:drop_table, [:system_settings]], drop_table
|
||||
def test_revert_order
|
||||
block = Proc.new{|t| t.string :name }
|
||||
@recorder.instance_eval do
|
||||
create_table("apples", &block)
|
||||
revert do
|
||||
create_table("bananas", &block)
|
||||
revert do
|
||||
create_table("clementines", &block)
|
||||
create_table("dates")
|
||||
end
|
||||
create_table("elderberries")
|
||||
end
|
||||
revert do
|
||||
create_table("figs", &block)
|
||||
create_table("grapes")
|
||||
end
|
||||
end
|
||||
assert_equal [[:create_table, ["apples"], block], [:drop_table, ["elderberries"], nil],
|
||||
[:create_table, ["clementines"], block], [:create_table, ["dates"], nil],
|
||||
[:drop_table, ["bananas"], block], [:drop_table, ["grapes"], nil],
|
||||
[:drop_table, ["figs"], block]], @recorder.commands
|
||||
end
|
||||
|
||||
def test_invert_create_table_with_options
|
||||
@recorder.record :create_table, [:people_reminders, {:id => false}]
|
||||
drop_table = @recorder.inverse.first
|
||||
assert_equal [:drop_table, [:people_reminders]], drop_table
|
||||
def test_invert_change_table
|
||||
@recorder.revert do
|
||||
@recorder.change_table :fruits do |t|
|
||||
t.string :name
|
||||
t.rename :kind, :cultivar
|
||||
end
|
||||
end
|
||||
assert_equal [
|
||||
[:rename_column, [:fruits, :cultivar, :kind]],
|
||||
[:remove_column, [:fruits, :name, :string, {}], nil],
|
||||
], @recorder.commands
|
||||
|
||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||
@recorder.revert do
|
||||
@recorder.change_table :fruits do |t|
|
||||
t.remove :kind
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def test_invert_create_table
|
||||
@recorder.revert do
|
||||
@recorder.record :create_table, [:system_settings]
|
||||
end
|
||||
drop_table = @recorder.commands.first
|
||||
assert_equal [:drop_table, [:system_settings], nil], drop_table
|
||||
end
|
||||
|
||||
def test_invert_create_table_with_options_and_block
|
||||
block = Proc.new{}
|
||||
drop_table = @recorder.inverse_of :create_table, [:people_reminders, id: false], &block
|
||||
assert_equal [:drop_table, [:people_reminders, id: false], block], drop_table
|
||||
end
|
||||
|
||||
def test_invert_drop_table
|
||||
block = Proc.new{}
|
||||
create_table = @recorder.inverse_of :drop_table, [:people_reminders, id: false], &block
|
||||
assert_equal [:create_table, [:people_reminders, id: false], block], create_table
|
||||
end
|
||||
|
||||
def test_invert_drop_table_without_a_block_nor_option
|
||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||
@recorder.inverse_of :drop_table, [:people_reminders]
|
||||
end
|
||||
end
|
||||
|
||||
def test_invert_create_join_table
|
||||
@recorder.record :create_join_table, [:musics, :artists]
|
||||
drop_table = @recorder.inverse.first
|
||||
assert_equal [:drop_table, [:artists_musics]], drop_table
|
||||
drop_join_table = @recorder.inverse_of :create_join_table, [:musics, :artists]
|
||||
assert_equal [:drop_join_table, [:musics, :artists], nil], drop_join_table
|
||||
end
|
||||
|
||||
def test_invert_create_join_table_with_table_name
|
||||
@recorder.record :create_join_table, [:musics, :artists, {:table_name => :catalog}]
|
||||
drop_table = @recorder.inverse.first
|
||||
assert_equal [:drop_table, [:catalog]], drop_table
|
||||
drop_join_table = @recorder.inverse_of :create_join_table, [:musics, :artists, table_name: :catalog]
|
||||
assert_equal [:drop_join_table, [:musics, :artists, table_name: :catalog], nil], drop_join_table
|
||||
end
|
||||
|
||||
def test_invert_drop_join_table
|
||||
block = Proc.new{}
|
||||
create_join_table = @recorder.inverse_of :drop_join_table, [:musics, :artists, table_name: :catalog], &block
|
||||
assert_equal [:create_join_table, [:musics, :artists, table_name: :catalog], block], create_join_table
|
||||
end
|
||||
|
||||
def test_invert_rename_table
|
||||
@recorder.record :rename_table, [:old, :new]
|
||||
rename = @recorder.inverse.first
|
||||
rename = @recorder.inverse_of :rename_table, [:old, :new]
|
||||
assert_equal [:rename_table, [:new, :old]], rename
|
||||
end
|
||||
|
||||
def test_invert_add_column
|
||||
@recorder.record :add_column, [:table, :column, :type, {}]
|
||||
remove = @recorder.inverse.first
|
||||
assert_equal [:remove_column, [:table, :column]], remove
|
||||
remove = @recorder.inverse_of :add_column, [:table, :column, :type, {}]
|
||||
assert_equal [:remove_column, [:table, :column, :type, {}], nil], remove
|
||||
end
|
||||
|
||||
def test_invert_remove_column
|
||||
add = @recorder.inverse_of :remove_column, [:table, :column, :type, {}]
|
||||
assert_equal [:add_column, [:table, :column, :type, {}], nil], add
|
||||
end
|
||||
|
||||
def test_invert_remove_column_without_type
|
||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||
@recorder.inverse_of :remove_column, [:table, :column]
|
||||
end
|
||||
end
|
||||
|
||||
def test_invert_rename_column
|
||||
@recorder.record :rename_column, [:table, :old, :new]
|
||||
rename = @recorder.inverse.first
|
||||
rename = @recorder.inverse_of :rename_column, [:table, :old, :new]
|
||||
assert_equal [:rename_column, [:table, :new, :old]], rename
|
||||
end
|
||||
|
||||
def test_invert_add_index
|
||||
@recorder.record :add_index, [:table, [:one, :two], {:options => true}]
|
||||
remove = @recorder.inverse.first
|
||||
assert_equal [:remove_index, [:table, {:column => [:one, :two]}]], remove
|
||||
remove = @recorder.inverse_of :add_index, [:table, [:one, :two], options: true]
|
||||
assert_equal [:remove_index, [:table, {column: [:one, :two], options: true}]], remove
|
||||
end
|
||||
|
||||
def test_invert_add_index_with_name
|
||||
@recorder.record :add_index, [:table, [:one, :two], {:name => "new_index"}]
|
||||
remove = @recorder.inverse.first
|
||||
assert_equal [:remove_index, [:table, {:name => "new_index"}]], remove
|
||||
remove = @recorder.inverse_of :add_index, [:table, [:one, :two], name: "new_index"]
|
||||
assert_equal [:remove_index, [:table, {column: [:one, :two], name: "new_index"}]], remove
|
||||
end
|
||||
|
||||
def test_invert_add_index_with_no_options
|
||||
@recorder.record :add_index, [:table, [:one, :two]]
|
||||
remove = @recorder.inverse.first
|
||||
assert_equal [:remove_index, [:table, {:column => [:one, :two]}]], remove
|
||||
remove = @recorder.inverse_of :add_index, [:table, [:one, :two]]
|
||||
assert_equal [:remove_index, [:table, {column: [:one, :two]}]], remove
|
||||
end
|
||||
|
||||
def test_invert_remove_index
|
||||
add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], options: true}]
|
||||
assert_equal [:add_index, [:table, [:one, :two], options: true]], add
|
||||
end
|
||||
|
||||
def test_invert_remove_index_with_name
|
||||
add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two], name: "new_index"}]
|
||||
assert_equal [:add_index, [:table, [:one, :two], name: "new_index"]], add
|
||||
end
|
||||
|
||||
def test_invert_remove_index_with_no_special_options
|
||||
add = @recorder.inverse_of :remove_index, [:table, {column: [:one, :two]}]
|
||||
assert_equal [:add_index, [:table, [:one, :two], {}]], add
|
||||
end
|
||||
|
||||
def test_invert_remove_index_with_no_column
|
||||
assert_raises(ActiveRecord::IrreversibleMigration) do
|
||||
@recorder.inverse_of :remove_index, [:table, name: "new_index"]
|
||||
end
|
||||
end
|
||||
|
||||
def test_invert_rename_index
|
||||
@recorder.record :rename_index, [:table, :old, :new]
|
||||
rename = @recorder.inverse.first
|
||||
rename = @recorder.inverse_of :rename_index, [:table, :old, :new]
|
||||
assert_equal [:rename_index, [:table, :new, :old]], rename
|
||||
end
|
||||
|
||||
def test_invert_add_timestamps
|
||||
@recorder.record :add_timestamps, [:table]
|
||||
remove = @recorder.inverse.first
|
||||
assert_equal [:remove_timestamps, [:table]], remove
|
||||
remove = @recorder.inverse_of :add_timestamps, [:table]
|
||||
assert_equal [:remove_timestamps, [:table], nil], remove
|
||||
end
|
||||
|
||||
def test_invert_remove_timestamps
|
||||
@recorder.record :remove_timestamps, [:table]
|
||||
add = @recorder.inverse.first
|
||||
assert_equal [:add_timestamps, [:table]], add
|
||||
add = @recorder.inverse_of :remove_timestamps, [:table]
|
||||
assert_equal [:add_timestamps, [:table], nil], add
|
||||
end
|
||||
|
||||
def test_invert_add_reference
|
||||
@recorder.record :add_reference, [:table, :taggable, { polymorphic: true }]
|
||||
remove = @recorder.inverse.first
|
||||
assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }]], remove
|
||||
remove = @recorder.inverse_of :add_reference, [:table, :taggable, { polymorphic: true }]
|
||||
assert_equal [:remove_reference, [:table, :taggable, { polymorphic: true }], nil], remove
|
||||
end
|
||||
|
||||
def test_invert_add_belongs_to_alias
|
||||
@recorder.record :add_belongs_to, [:table, :user]
|
||||
remove = @recorder.inverse.first
|
||||
assert_equal [:remove_reference, [:table, :user]], remove
|
||||
remove = @recorder.inverse_of :add_belongs_to, [:table, :user]
|
||||
assert_equal [:remove_reference, [:table, :user], nil], remove
|
||||
end
|
||||
|
||||
def test_invert_remove_reference
|
||||
@recorder.record :remove_reference, [:table, :taggable, { polymorphic: true }]
|
||||
add = @recorder.inverse.first
|
||||
assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }]], add
|
||||
add = @recorder.inverse_of :remove_reference, [:table, :taggable, { polymorphic: true }]
|
||||
assert_equal [:add_reference, [:table, :taggable, { polymorphic: true }], nil], add
|
||||
end
|
||||
|
||||
def test_invert_remove_belongs_to_alias
|
||||
@recorder.record :remove_belongs_to, [:table, :user]
|
||||
add = @recorder.inverse.first
|
||||
assert_equal [:add_reference, [:table, :user]], add
|
||||
add = @recorder.inverse_of :remove_belongs_to, [:table, :user]
|
||||
assert_equal [:add_reference, [:table, :user], nil], add
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -78,6 +78,48 @@ def test_create_join_table_with_index
|
||||
|
||||
assert_equal [%w(artist_id music_id)], connection.indexes(:artists_musics).map(&:columns)
|
||||
end
|
||||
|
||||
def test_drop_join_table
|
||||
connection.create_join_table :artists, :musics
|
||||
connection.drop_join_table :artists, :musics
|
||||
|
||||
assert !connection.tables.include?('artists_musics')
|
||||
end
|
||||
|
||||
def test_drop_join_table_with_strings
|
||||
connection.create_join_table :artists, :musics
|
||||
connection.drop_join_table 'artists', 'musics'
|
||||
|
||||
assert !connection.tables.include?('artists_musics')
|
||||
end
|
||||
|
||||
def test_drop_join_table_with_the_proper_order
|
||||
connection.create_join_table :videos, :musics
|
||||
connection.drop_join_table :videos, :musics
|
||||
|
||||
assert !connection.tables.include?('musics_videos')
|
||||
end
|
||||
|
||||
def test_drop_join_table_with_the_table_name
|
||||
connection.create_join_table :artists, :musics, table_name: :catalog
|
||||
connection.drop_join_table :artists, :musics, table_name: :catalog
|
||||
|
||||
assert !connection.tables.include?('catalog')
|
||||
end
|
||||
|
||||
def test_drop_join_table_with_the_table_name_as_string
|
||||
connection.create_join_table :artists, :musics, table_name: 'catalog'
|
||||
connection.drop_join_table :artists, :musics, table_name: 'catalog'
|
||||
|
||||
assert !connection.tables.include?('catalog')
|
||||
end
|
||||
|
||||
def test_create_join_table_with_column_options
|
||||
connection.create_join_table :artists, :musics, column_options: {null: true}
|
||||
connection.drop_join_table :artists, :musics, column_options: {null: true}
|
||||
|
||||
assert !connection.tables.include?('artists_musics')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -165,6 +165,19 @@ Please refer to the [Changelog](https://github.com/rails/rails/blob/master/railt
|
||||
|
||||
### Notable changes
|
||||
|
||||
* Improve ways to write `change` migrations, making the old `up` & `down` methods no longer necessary.
|
||||
|
||||
* The methods `drop_table` and `remove_column` are now reversible, as long as the necessary information is given.
|
||||
The method `remove_column` used to accept multiple column names; instead use `remove_columns` (which is not revertible).
|
||||
The method `change_table` is also reversible, as long as its block doesn't call `remove`, `change` or `change_default`
|
||||
|
||||
* New method `reversible` makes it possible to specify code to be run when migrating up or down.
|
||||
See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#using-the-reversible-method)
|
||||
|
||||
* New method `revert` will revert a whole migration or the given block.
|
||||
If migrating down, the given migration / block is run normally.
|
||||
See the [Guide on Migration](https://github.com/rails/rails/blob/master/guides/source/migrations.md#reverting-previous-migrations)
|
||||
|
||||
* Adds some metadata columns to `schema_migrations` table.
|
||||
|
||||
* `migrated_at`
|
||||
|
@ -56,25 +56,40 @@ Before this migration is run, there will be no table. After, the table will
|
||||
exist. Active Record knows how to reverse this migration as well: if we roll
|
||||
this migration back, it will remove the table.
|
||||
|
||||
On databases that support transactions with statements that change the schema ,
|
||||
On databases that support transactions with statements that change the schema,
|
||||
migrations are wrapped in a transaction. If the database does not support this
|
||||
then when a migration fails the parts of it that succeeded will not be rolled
|
||||
back. You will have to rollback the changes that were made by hand.
|
||||
|
||||
If you wish for a migration to do something that Active Record doesn't know how
|
||||
to reverse, you can use `up` and `down` instead of `change`:
|
||||
to reverse, you can use `reversible`:
|
||||
|
||||
```ruby
|
||||
class ChangeProductsPrice < ActiveRecord::Migration
|
||||
def up
|
||||
change_table :products do |t|
|
||||
t.string :price, null: false
|
||||
def change
|
||||
reversible do |dir|
|
||||
change_table :products do |t|
|
||||
dir.up { t.change :price, :string }
|
||||
dir.down { t.change :price, :integer }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
Alternatively, you can use `up` and `down` instead of `change`:
|
||||
|
||||
``ruby
|
||||
class ChangeProductsPrice < ActiveRecord::Migration
|
||||
def up
|
||||
change_table :products do |t|
|
||||
t.change :price, :string
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
change_table :products do |t|
|
||||
t.integer :price, null: false
|
||||
t.change :price, :integer
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -93,7 +108,7 @@ of the migration. The name of the migration class (CamelCased version)
|
||||
should match the latter part of the file name. For example
|
||||
`20080906120000_create_products.rb` should define class `CreateProducts` and
|
||||
`20080906120001_add_details_to_products.rb` should define
|
||||
`AddDetailsToProducts`.
|
||||
`AddDetailsToProducts`.
|
||||
|
||||
Of course, calculating timestamps is no fun, so Active Record provides a
|
||||
generator to handle making it for you:
|
||||
@ -139,12 +154,8 @@ generates
|
||||
|
||||
```ruby
|
||||
class RemovePartNumberFromProducts < ActiveRecord::Migration
|
||||
def up
|
||||
remove_column :products, :part_number
|
||||
end
|
||||
|
||||
def down
|
||||
add_column :products, :part_number, :string
|
||||
def change
|
||||
remove_column :products, :part_number, :string
|
||||
end
|
||||
end
|
||||
```
|
||||
@ -170,10 +181,6 @@ As always, what has been generated for you is just a starting point. You can add
|
||||
or remove from it as you see fit by editing the
|
||||
`db/migrate/YYYYMMDDHHMMSS_add_details_to_products.rb` file.
|
||||
|
||||
NOTE: The generated migration file for destructive migrations will still be
|
||||
old-style using the `up` and `down` methods. This is because Rails needs to
|
||||
know the original data types defined when you made the original changes.
|
||||
|
||||
Also, the generator accepts column type as `references`(also available as
|
||||
`belongs_to`). For instance
|
||||
|
||||
@ -346,7 +353,7 @@ Products.connection.execute('UPDATE `products` SET `price`=`free` WHERE 1')
|
||||
For more details and examples of individual methods, check the API documentation.
|
||||
In particular the documentation for
|
||||
[`ActiveRecord::ConnectionAdapters::SchemaStatements`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html)
|
||||
(which provides the methods available in the `up` and `down` methods),
|
||||
(which provides the methods available in the `change`, `up` and `down` methods),
|
||||
[`ActiveRecord::ConnectionAdapters::TableDefinition`](http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html)
|
||||
(which provides the methods available on the object yielded by `create_table`)
|
||||
and
|
||||
@ -362,25 +369,82 @@ definitions:
|
||||
|
||||
* `add_column`
|
||||
* `add_index`
|
||||
* `add_reference`
|
||||
* `add_timestamps`
|
||||
* `create_table`
|
||||
* `create_join_table`
|
||||
* `drop_table` (must supply a block)
|
||||
* `drop_join_table` (must supply a block)
|
||||
* `remove_timestamps`
|
||||
* `rename_column`
|
||||
* `rename_index`
|
||||
* `remove_reference`
|
||||
* `rename_table`
|
||||
|
||||
If you're going to need to use any other methods, you'll have to write the
|
||||
`up` and `down` methods instead of using the `change` method.
|
||||
`change_table` is also reversible, as long as the block does not call `change`,
|
||||
`change_default` or `remove`.
|
||||
|
||||
If you're going to need to use any other methods, you should use `reversible`
|
||||
or write the `up` and `down` methods instead of using the `change` method.
|
||||
|
||||
### Using `reversible`
|
||||
|
||||
Complex migrations may require processing that Active Record doesn't know how
|
||||
to reverse. You can use `reversible` to specify what to do when running a
|
||||
migration what else to do when reverting it. For example,
|
||||
|
||||
```ruby
|
||||
class ExampleMigration < ActiveRecord::Migration
|
||||
def change
|
||||
create_table :products do |t|
|
||||
t.references :category
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
#add a foreign key
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
ADD CONSTRAINT fk_products_categories
|
||||
FOREIGN KEY (category_id)
|
||||
REFERENCES categories(id)
|
||||
SQL
|
||||
end
|
||||
dir.down do
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
DROP FOREIGN KEY fk_products_categories
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
||||
add_column :users, :home_page_url, :string
|
||||
rename_column :users, :email, :email_address
|
||||
end
|
||||
```
|
||||
|
||||
Using `reversible` will insure that the instructions are executed in the
|
||||
right order too. If the previous example migration is reverted,
|
||||
the `down` block will be run after the `home_page_url` column is removed and
|
||||
right before the table `products` is dropped.
|
||||
|
||||
Sometimes your migration will do something which is just plain irreversible; for
|
||||
example, it might destroy some data. In such cases, you can raise
|
||||
`ActiveRecord::IrreversibleMigration` in your `down` block. If someone tries
|
||||
to revert your migration, an error message will be displayed saying that it
|
||||
can't be done.
|
||||
|
||||
### Using the `up`/`down` Methods
|
||||
|
||||
You can also use the old style of migration using `up` and `down` methods
|
||||
instead of the `change` method.
|
||||
The `up` method should describe the transformation you'd like to make to your
|
||||
schema, and the `down` method of your migration should revert the
|
||||
transformations done by the `up` method. In other words, the database schema
|
||||
should be unchanged if you do an `up` followed by a `down`. For example, if you
|
||||
create a table in the `up` method, you should drop it in the `down` method. It
|
||||
is wise to reverse the transformations in precisely the reverse order they were
|
||||
made in the `up` method. For example,
|
||||
made in the `up` method. The example in the `reversible` section is equivalent to:
|
||||
|
||||
```ruby
|
||||
class ExampleMigration < ActiveRecord::Migration
|
||||
@ -415,19 +479,92 @@ class ExampleMigration < ActiveRecord::Migration
|
||||
end
|
||||
```
|
||||
|
||||
Sometimes your migration will do something which is just plain irreversible; for
|
||||
example, it might destroy some data. In such cases, you can raise
|
||||
If your migration is irreversible, you should raise
|
||||
`ActiveRecord::IrreversibleMigration` from your `down` method. If someone tries
|
||||
to revert your migration, an error message will be displayed saying that it
|
||||
can't be done.
|
||||
|
||||
### Reverting Previous Migrations
|
||||
|
||||
You can use Active Record's ability to rollback migrations using the `revert` method:
|
||||
|
||||
```ruby
|
||||
require_relative '2012121212_example_migration'
|
||||
|
||||
class FixupExampleMigration < ActiveRecord::Migration
|
||||
def change
|
||||
revert ExampleMigration
|
||||
|
||||
create_table(:apples) do |t|
|
||||
t.string :variety
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The `revert` method also accepts a block of instructions to reverse.
|
||||
This could be useful to revert selected parts of previous migrations.
|
||||
For example, let's imagine that `ExampleMigration` is committed and it
|
||||
is later decided it would be best to serialize the product list instead.
|
||||
One could write:
|
||||
|
||||
```ruby
|
||||
class SerializeProductListMigration < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :categories, :product_list
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
# transfer data from Products to Category#product_list
|
||||
end
|
||||
dir.down do
|
||||
# create Products from Category#product_list
|
||||
end
|
||||
end
|
||||
|
||||
revert do
|
||||
# copy-pasted code from ExampleMigration
|
||||
create_table :products do |t|
|
||||
t.references :category
|
||||
end
|
||||
|
||||
reversible do |dir|
|
||||
dir.up do
|
||||
#add a foreign key
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
ADD CONSTRAINT fk_products_categories
|
||||
FOREIGN KEY (category_id)
|
||||
REFERENCES categories(id)
|
||||
SQL
|
||||
end
|
||||
dir.down do
|
||||
execute <<-SQL
|
||||
ALTER TABLE products
|
||||
DROP FOREIGN KEY fk_products_categories
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
||||
# The rest of the migration was ok
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The same migration could also have been written without using `revert`
|
||||
but this would have involved a few more steps: reversing the order
|
||||
of `create_table` and `reversible`, replacing `create_table`
|
||||
by `drop_table`, and finally replacing `up` by `down` and vice-versa.
|
||||
This is all taken care of by `revert`.
|
||||
|
||||
Running Migrations
|
||||
------------------
|
||||
|
||||
Rails provides a set of Rake tasks to run certain sets of migrations.
|
||||
|
||||
The very first migration related Rake task you will use will probably be
|
||||
`rake db:migrate`. In its most basic form it just runs the `up` or `change`
|
||||
`rake db:migrate`. In its most basic form it just runs the `change` or `up`
|
||||
method for all the migrations that have not yet been run. If there are
|
||||
no such migrations, it exits. It will run these migrations in order based
|
||||
on the date of the migration.
|
||||
@ -436,7 +573,7 @@ Note that running the `db:migrate` also invokes the `db:schema:dump` task, which
|
||||
will update your `db/schema.rb` file to match the structure of your database.
|
||||
|
||||
If you specify a target version, Active Record will run the required migrations
|
||||
(up, down or change) until it has reached the specified version. The version
|
||||
(change, up, down) until it has reached the specified version. The version
|
||||
is the numerical prefix on the migration's filename. For example, to migrate
|
||||
to version 20080906120000 run
|
||||
|
||||
@ -445,7 +582,8 @@ $ rake db:migrate VERSION=20080906120000
|
||||
```
|
||||
|
||||
If version 20080906120000 is greater than the current version (i.e., it is
|
||||
migrating upwards), this will run the `up` method on all migrations up to and
|
||||
migrating upwards), this will run the `change` (or `up`) method
|
||||
on all migrations up to and
|
||||
including 20080906120000, and will not execute any later migrations. If
|
||||
migrating downwards, this will run the `down` method on all the migrations
|
||||
down to, but not including, 20080906120000.
|
||||
@ -460,14 +598,15 @@ number associated with the previous migration you can run
|
||||
$ rake db:rollback
|
||||
```
|
||||
|
||||
This will run the `down` method from the latest migration. If you need to undo
|
||||
This will rollback the latest migration, either by reverting the `change`
|
||||
method or by running the `down` method. If you need to undo
|
||||
several migrations you can provide a `STEP` parameter:
|
||||
|
||||
```bash
|
||||
$ rake db:rollback STEP=3
|
||||
```
|
||||
|
||||
will run the `down` method from the last 3 migrations.
|
||||
will revert the last 3 migrations.
|
||||
|
||||
The `db:migrate:redo` task is a shortcut for doing a rollback and then migrating
|
||||
back up again. As with the `db:rollback` task, you can use the `STEP` parameter
|
||||
@ -495,14 +634,15 @@ contents of the current schema.rb file. If a migration can't be rolled back,
|
||||
|
||||
If you need to run a specific migration up or down, the `db:migrate:up` and
|
||||
`db:migrate:down` tasks will do that. Just specify the appropriate version and
|
||||
the corresponding migration will have its `up` or `down` method invoked, for
|
||||
example,
|
||||
the corresponding migration will have its `change`, `up` or `down` method
|
||||
invoked, for example,
|
||||
|
||||
```bash
|
||||
$ rake db:migrate:up VERSION=20080906120000
|
||||
```
|
||||
|
||||
will run the `up` method from the 20080906120000 migration. This task will
|
||||
will run the 20080906120000 migration by running the `change` method (or the
|
||||
`up` method). This task will
|
||||
first check whether the migration is already performed and will do nothing if
|
||||
Active Record believes that it has already been run.
|
||||
|
||||
@ -596,6 +736,10 @@ you require. Editing a freshly generated migration that has not yet been
|
||||
committed to source control (or, more generally, which has not been propagated
|
||||
beyond your development machine) is relatively harmless.
|
||||
|
||||
The `revert` method can be helpful when writing a new migration to undo
|
||||
previous migrations in whole or in part
|
||||
(see [Reverting Previous Migrations](#reverting-previous-migrations) above).
|
||||
|
||||
Using Models in Your Migrations
|
||||
-------------------------------
|
||||
|
||||
@ -622,6 +766,9 @@ column.
|
||||
class AddFlagToProduct < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :products, :flag, :boolean
|
||||
reversible do |dir|
|
||||
dir.up { Product.update_all flag: false }
|
||||
end
|
||||
Product.update_all flag: false
|
||||
end
|
||||
end
|
||||
@ -645,7 +792,9 @@ column.
|
||||
class AddFuzzToProduct < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :products, :fuzz, :string
|
||||
Product.update_all fuzz: 'fuzzy'
|
||||
reversible do |dir|
|
||||
dir.up { Product.update_all fuzz: 'fuzzy' }
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
@ -697,7 +846,9 @@ class AddFlagToProduct < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :products, :flag, :boolean
|
||||
Product.reset_column_information
|
||||
Product.update_all flag: false
|
||||
reversible do |dir|
|
||||
dir.up { Product.update_all flag: false }
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
@ -712,7 +863,9 @@ class AddFuzzToProduct < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :products, :fuzz, :string
|
||||
Product.reset_column_information
|
||||
Product.update_all fuzz: 'fuzzy'
|
||||
reversible do |dir|
|
||||
dir.up { Product.update_all fuzz: 'fuzzy' }
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
@ -810,9 +963,9 @@ Rake task) into `db/structure.sql`. For example, for PostgreSQL, the `pg_dump`
|
||||
utility is used. For MySQL, this file will contain the output of `SHOW CREATE
|
||||
TABLE` for the various tables.
|
||||
|
||||
Loading these schemas is simply a question of executing the SQL statements they
|
||||
contain. By definition, this will create a perfect copy of the database's
|
||||
structure. Using the `:sql` schema format will, however, prevent loading the
|
||||
Loading these schemas is simply a question of executing the SQL statements they
|
||||
contain. By definition, this will create a perfect copy of the database's
|
||||
structure. Using the `:sql` schema format will, however, prevent loading the
|
||||
schema into a RDBMS other than the one used to create it.
|
||||
|
||||
### Schema Dumps and Source Control
|
||||
|
@ -1,5 +1,9 @@
|
||||
## Rails 4.0.0 (unreleased) ##
|
||||
|
||||
* Generated migrations now always use the `change` method.
|
||||
|
||||
*Marc-André Lafortune*
|
||||
|
||||
* Add `app/models/concerns` and `app/controllers/concerns` to the default directory structure and load path.
|
||||
See http://37signals.com/svn/posts/3372-put-chubby-models-on-a-diet-with-concerns for usage instructions.
|
||||
|
||||
|
@ -28,7 +28,7 @@ def test_migration_with_class_name
|
||||
run_generator [migration]
|
||||
assert_migration "db/migrate/change_title_body_from_posts.rb", /class #{migration} < ActiveRecord::Migration/
|
||||
end
|
||||
|
||||
|
||||
def test_migration_with_invalid_file_name
|
||||
migration = "add_something:datetime"
|
||||
assert_raise ActiveRecord::IllegalMigrationNameError do
|
||||
@ -41,9 +41,9 @@ def test_add_migration_with_attributes
|
||||
run_generator [migration, "title:string", "body:text"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :change, content do |up|
|
||||
assert_match(/add_column :posts, :title, :string/, up)
|
||||
assert_match(/add_column :posts, :body, :text/, up)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/add_column :posts, :title, :string/, change)
|
||||
assert_match(/add_column :posts, :body, :text/, change)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -53,15 +53,10 @@ def test_remove_migration_with_indexed_attribute
|
||||
run_generator [migration, "title:string:index", "body:text"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :up, content do |up|
|
||||
assert_match(/remove_column :posts, :title/, up)
|
||||
assert_match(/remove_column :posts, :body/, up)
|
||||
end
|
||||
|
||||
assert_method :down, content do |down|
|
||||
assert_match(/add_column :posts, :title, :string/, down)
|
||||
assert_match(/add_column :posts, :body, :text/, down)
|
||||
assert_match(/add_index :posts, :title/, down)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/remove_column :posts, :title, :string/, change)
|
||||
assert_match(/remove_column :posts, :body, :text/, change)
|
||||
assert_match(/remove_index :posts, :title/, change)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -71,14 +66,9 @@ def test_remove_migration_with_attributes
|
||||
run_generator [migration, "title:string", "body:text"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :up, content do |up|
|
||||
assert_match(/remove_column :posts, :title/, up)
|
||||
assert_match(/remove_column :posts, :body/, up)
|
||||
end
|
||||
|
||||
assert_method :down, content do |down|
|
||||
assert_match(/add_column :posts, :title, :string/, down)
|
||||
assert_match(/add_column :posts, :body, :text/, down)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/remove_column :posts, :title, :string/, change)
|
||||
assert_match(/remove_column :posts, :body, :text/, change)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -88,14 +78,9 @@ def test_remove_migration_with_references_options
|
||||
run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :up, content do |up|
|
||||
assert_match(/remove_reference :books, :author/, up)
|
||||
assert_match(/remove_reference :books, :distributor, polymorphic: true/, up)
|
||||
end
|
||||
|
||||
assert_method :down, content do |down|
|
||||
assert_match(/add_reference :books, :author, index: true/, down)
|
||||
assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, down)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/remove_reference :books, :author, index: true/, change)
|
||||
assert_match(/remove_reference :books, :distributor, polymorphic: true, index: true/, change)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -105,13 +90,13 @@ def test_add_migration_with_attributes_and_indices
|
||||
run_generator [migration, "title:string:index", "body:text", "user_id:integer:uniq"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :change, content do |up|
|
||||
assert_match(/add_column :posts, :title, :string/, up)
|
||||
assert_match(/add_column :posts, :body, :text/, up)
|
||||
assert_match(/add_column :posts, :user_id, :integer/, up)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/add_column :posts, :title, :string/, change)
|
||||
assert_match(/add_column :posts, :body, :text/, change)
|
||||
assert_match(/add_column :posts, :user_id, :integer/, change)
|
||||
assert_match(/add_index :posts, :title/, change)
|
||||
assert_match(/add_index :posts, :user_id, unique: true/, change)
|
||||
end
|
||||
assert_match(/add_index :posts, :title/, content)
|
||||
assert_match(/add_index :posts, :user_id, unique: true/, content)
|
||||
end
|
||||
end
|
||||
|
||||
@ -120,10 +105,10 @@ def test_add_migration_with_attributes_and_wrong_index_declaration
|
||||
run_generator [migration, "title:string:inex", "content:text", "user_id:integer:unik"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :change, content do |up|
|
||||
assert_match(/add_column :books, :title, :string/, up)
|
||||
assert_match(/add_column :books, :content, :text/, up)
|
||||
assert_match(/add_column :books, :user_id, :integer/, up)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/add_column :books, :title, :string/, change)
|
||||
assert_match(/add_column :books, :content, :text/, change)
|
||||
assert_match(/add_column :books, :user_id, :integer/, change)
|
||||
end
|
||||
assert_no_match(/add_index :books, :title/, content)
|
||||
assert_no_match(/add_index :books, :user_id/, content)
|
||||
@ -135,13 +120,13 @@ def test_add_migration_with_attributes_without_type_and_index
|
||||
run_generator [migration, "title:index", "body:text", "user_uuid:uniq"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :change, content do |up|
|
||||
assert_match(/add_column :posts, :title, :string/, up)
|
||||
assert_match(/add_column :posts, :body, :text/, up)
|
||||
assert_match(/add_column :posts, :user_uuid, :string/, up)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/add_column :posts, :title, :string/, change)
|
||||
assert_match(/add_column :posts, :body, :text/, change)
|
||||
assert_match(/add_column :posts, :user_uuid, :string/, change)
|
||||
assert_match(/add_index :posts, :title/, change)
|
||||
assert_match(/add_index :posts, :user_uuid, unique: true/, change)
|
||||
end
|
||||
assert_match(/add_index :posts, :title/, content)
|
||||
assert_match(/add_index :posts, :user_uuid, unique: true/, content)
|
||||
end
|
||||
end
|
||||
|
||||
@ -150,11 +135,11 @@ def test_add_migration_with_attributes_index_declaration_and_attribute_options
|
||||
run_generator [migration, "title:string{40}:index", "content:string{255}", "price:decimal{1,2}:index", "discount:decimal{3.4}:uniq"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :change, content do |up|
|
||||
assert_match(/add_column :books, :title, :string, limit: 40/, up)
|
||||
assert_match(/add_column :books, :content, :string, limit: 255/, up)
|
||||
assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, up)
|
||||
assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, up)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/add_column :books, :title, :string, limit: 40/, change)
|
||||
assert_match(/add_column :books, :content, :string, limit: 255/, change)
|
||||
assert_match(/add_column :books, :price, :decimal, precision: 1, scale: 2/, change)
|
||||
assert_match(/add_column :books, :discount, :decimal, precision: 3, scale: 4/, change)
|
||||
end
|
||||
assert_match(/add_index :books, :title/, content)
|
||||
assert_match(/add_index :books, :price/, content)
|
||||
@ -167,9 +152,9 @@ def test_add_migration_with_references_options
|
||||
run_generator [migration, "author:belongs_to", "distributor:references{polymorphic}"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :change, content do |up|
|
||||
assert_match(/add_reference :books, :author, index: true/, up)
|
||||
assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, up)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/add_reference :books, :author, index: true/, change)
|
||||
assert_match(/add_reference :books, :distributor, polymorphic: true, index: true/, change)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -179,10 +164,10 @@ def test_create_join_table_migration
|
||||
run_generator [migration, "artist_id", "musics:uniq"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :change, content do |up|
|
||||
assert_match(/create_join_table :artists, :musics/, up)
|
||||
assert_match(/# t.index \[:artist_id, :music_id\]/, up)
|
||||
assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, up)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/create_join_table :artists, :musics/, change)
|
||||
assert_match(/# t.index \[:artist_id, :music_id\]/, change)
|
||||
assert_match(/ t.index \[:music_id, :artist_id\], unique: true/, change)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -192,12 +177,8 @@ def test_should_create_empty_migrations_if_name_not_start_with_add_or_remove
|
||||
run_generator [migration, "title:string", "content:text"]
|
||||
|
||||
assert_migration "db/migrate/#{migration}.rb" do |content|
|
||||
assert_method :up, content do |up|
|
||||
assert_match(/^\s*$/, up)
|
||||
end
|
||||
|
||||
assert_method :down, content do |down|
|
||||
assert_match(/^\s*$/, down)
|
||||
assert_method :change, content do |change|
|
||||
assert_match(/^\s*$/, change)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user