Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Add migration history to schema.rb dump

  • Loading branch information...
commit f02d2185ebbe01f455a9a91216ff7094b014ea72 1 parent 0a5afa2
@joshsusser joshsusser authored
View
11 activerecord/CHANGELOG.md
@@ -1,5 +1,16 @@
## Rails 4.0.0 (unreleased) ##
+* Add migration history to schema.rb dump.
+ Loading schema.rb with full migration history
+ restores the exact list of migrations that created
+ that schema (including names and fingerprints). This
+ avoids possible mistakes caused by assuming all
+ migrations with a lower version have been run when
+ loading schema.rb. Old schema.rb files without migration
+ history but with the :version setting still work as before.
+
+ *Josh Susser*
+
* Add metadata columns to schema_migrations table.
New columns are: migrated_at (timestamp),
fingerprint (md5 hash of migration source), and
View
4 activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -490,8 +490,8 @@ def dump_schema_information #:nodoc:
sm_table = ActiveRecord::Migrator.schema_migrations_table_name
ActiveRecord::SchemaMigration.order('version').map { |sm|
- "INSERT INTO #{sm_table} (version, migrated_at, name) VALUES ('#{sm.version}',LOCALTIMESTAMP,'#{sm.name}');"
- }.join "\n\n"
+ "INSERT INTO #{sm_table} (version, migrated_at, fingerprint, name) VALUES ('#{sm.version}',LOCALTIMESTAMP,'#{sm.fingerprint}','#{sm.name}');"
+ }.join("\n\n")
end
# Should not be called normally, but this operation is non-destructive.
View
40 activerecord/lib/active_record/schema.rb
@@ -34,27 +34,45 @@ def migrations_paths
end
def define(info, &block)
+ @using_deprecated_version_setting = info[:version].present?
+ SchemaMigration.drop_table
+ initialize_schema_migrations_table
+
instance_eval(&block)
- unless info[:version].blank?
- initialize_schema_migrations_table
- assume_migrated_upto_version(info[:version], migrations_paths)
- end
+ # handle files from pre-4.0 that used :version option instead of dumping migration table
+ assume_migrated_upto_version(info[:version], migrations_paths) if @using_deprecated_version_setting
end
# Eval the given block. All methods available to the current connection
# adapter are available within the block, so you can easily use the
# database definition DSL to build up your schema (+create_table+,
# +add_index+, etc.).
- #
- # The +info+ hash is optional, and if given is used to define metadata
- # about the current schema (currently, only the schema's version):
- #
- # ActiveRecord::Schema.define(version: 20380119000001) do
- # ...
- # end
def self.define(info={}, &block)
new.define(info, &block)
end
+
+ # Create schema migration history. Include migration statements in a block to this method.
+ #
+ # migrations do
+ # migration 20121128235959, "44f1397e3b92442ca7488a029068a5ad", "add_horn_color_to_unicorns"
+ # migration 20121129235959, "4a1eb3965d94406b00002b370854eae8", "add_magic_power_to_unicorns"
+ # end
+ def migrations
+ raise(ArgumentError, "Can't set migrations while using :version option") if @using_deprecated_version_setting
+ yield
+ end
+
+ # Add a migration to the ActiveRecord::SchemaMigration table.
+ #
+ # The +version+ argument is an integer.
+ # The +fingerprint+ and +name+ arguments are required but may be empty strings.
+ # The migration's +migrated_at+ attribute is set to the current time,
+ # instead of being set explicitly as an argument to the method.
+ #
+ # migration 20121129235959, "4a1eb3965d94406b00002b370854eae8", "add_magic_power_to_unicorns"
+ def migration(version, fingerprint, name)
+ SchemaMigration.create!(version: version, migrated_at: Time.now, fingerprint: fingerprint, name: name)
+ end
end
end
View
17 activerecord/lib/active_record/schema_dumper.rb
@@ -24,6 +24,7 @@ def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
def dump(stream)
header(stream)
+ migrations(stream)
tables(stream)
trailer(stream)
stream
@@ -44,7 +45,7 @@ def header(stream)
stream.puts "# encoding: #{stream.external_encoding.name}"
end
- stream.puts <<HEADER
+ header_text = <<HEADER_RUBY
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
@@ -59,13 +60,25 @@ def header(stream)
ActiveRecord::Schema.define(#{define_params}) do
-HEADER
+HEADER_RUBY
+ stream.puts header_text
end
def trailer(stream)
stream.puts "end"
end
+ def migrations(stream)
+ all_migrations = ActiveRecord::SchemaMigration.all.to_a
+ if all_migrations.any?
+ stream.puts(" migrations do")
+ all_migrations.each do |migration|
+ stream.puts(migration.schema_line(" "))
+ end
+ stream.puts(" end")
+ end
+ end
+
def tables(stream)
@connection.tables.sort.each do |tbl|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
View
12 activerecord/lib/active_record/schema_migration.rb
@@ -53,5 +53,17 @@ def self.drop_table
def version
super.to_i
end
+
+ # Construct ruby source to include in schema.rb dump for this migration.
+ # Pass a string of spaces as +indent+ to allow calling code to control how deeply indented the line is.
+ # The generated line includes the migration version, fingerprint, and name. Either fingerprint or name
+ # can be an empty string.
+ #
+ # Example output:
+ #
+ # migration 20121129235959, "ee4be703f9e6e2fc0f4baddebe6eb8f7", "add_magic_power_to_unicorns"
+ def schema_line(indent)
+ %Q(#{indent}migration %s, "%s", "%s") % [version, fingerprint, name]
+ end
end
end
View
51 activerecord/test/cases/ar_schema_test.rb
@@ -46,4 +46,55 @@ def test_schema_subclass
end
end
+ class ActiveRecordSchemaMigrationsTest < ActiveRecordSchemaTest
+ def setup
+ super
+ ActiveRecord::SchemaMigration.delete_all
+ end
+
+ def test_migration_adds_row_to_migrations_table
+ schema = ActiveRecord::Schema.new
+ schema.migration(1001, "", "")
+ schema.migration(1002, "123456789012345678901234567890ab", "add_magic_power_to_unicorns")
+
+ migrations = ActiveRecord::SchemaMigration.all.to_a
+ assert_equal 2, migrations.length
+
+ assert_equal 1001, migrations[0].version
+ assert_match %r{^2\d\d\d-}, migrations[0].migrated_at.to_s(:db)
+ assert_equal "", migrations[0].fingerprint
+ assert_equal "", migrations[0].name
+
+ assert_equal 1002, migrations[1].version
+ assert_match %r{^2\d\d\d-}, migrations[1].migrated_at.to_s(:db)
+ assert_equal "123456789012345678901234567890ab", migrations[1].fingerprint
+ assert_equal "add_magic_power_to_unicorns", migrations[1].name
+ end
+
+ def test_define_clears_schema_migrations
+ assert_nothing_raised do
+ ActiveRecord::Schema.define do
+ migrations do
+ migration(123001, "", "")
+ end
+ end
+ ActiveRecord::Schema.define do
+ migrations do
+ migration(123001, "", "")
+ end
+ end
+ end
+ end
+
+ def test_define_raises_if_both_version_and_explicit_migrations
+ assert_raise(ArgumentError) do
+ ActiveRecord::Schema.define(version: 123001) do
+ migrations do
+ migration(123001, "", "")
+ end
+ end
+ end
+ end
+ end
+
end
View
18 activerecord/test/cases/schema_dumper_test.rb
@@ -1,5 +1,5 @@
require "cases/helper"
-
+# require "cases/migration/helper"
class SchemaDumperTest < ActiveRecord::TestCase
def setup
@@ -18,11 +18,15 @@ def standard_dump
def test_dump_schema_information_outputs_lexically_ordered_versions
versions = %w{ 20100101010101 20100201010101 20100301010101 }
versions.reverse.each do |v|
- ActiveRecord::SchemaMigration.create!(:version => v, :name => "anon", :migrated_at => Time.now)
+ ActiveRecord::SchemaMigration.create!(
+ :version => v, :migrated_at => Time.now,
+ :fingerprint => "123456789012345678901234567890ab", :name => "anon")
end
schema_info = ActiveRecord::Base.connection.dump_schema_information
assert_match(/20100201010101.*20100301010101/m, schema_info)
+ target_line = %q{INSERT INTO schema_migrations (version, migrated_at, fingerprint, name) VALUES ('20100101010101',LOCALTIMESTAMP,'123456789012345678901234567890ab','anon');}
+ assert_match target_line, schema_info
end
def test_magic_comment
@@ -36,6 +40,16 @@ def test_schema_dump
assert_no_match %r{create_table "schema_migrations"}, output
end
+ def test_schema_dump_includes_migrations
+ ActiveRecord::SchemaMigration.delete_all
+ ActiveRecord::Migrator.migrate(MIGRATIONS_ROOT + "/always_safe")
+
+ output = standard_dump
+ assert_match %r{migrations do}, output, "Missing migrations block"
+ assert_match %r{migration 1001, "[0-9a-f]{32}", "always_safe"}, output, "Missing migration line"
+ assert_match %r{migration 1002, "[0-9a-f]{32}", "still_safe"}, output, "Missing migration line"
+ end
+
def test_schema_dump_excludes_sqlite_sequence
output = standard_dump
assert_no_match %r{create_table "sqlite_sequence"}, output
View
5 activerecord/test/migrations/always_safe/1001_always_safe.rb
@@ -0,0 +1,5 @@
+class AlwaysSafe < ActiveRecord::Migration
+ def change
+ # do nothing to avoid side-effect conflicts from running multiple times
+ end
+end
View
5 activerecord/test/migrations/always_safe/1002_still_safe.rb
@@ -0,0 +1,5 @@
+class StillSafe < ActiveRecord::Migration
+ def change
+ # do nothing to avoid side-effect conflicts from running multiple times
+ end
+end

5 comments on commit f02d218

@rubys

This causes rake test after rake db:migrate to fail with "Migrations are pending" and "ArgumentError: Can't set migrations while using :version option". See http://intertwingly.net/projects/AWDwR4/checkdepot/section-6.1.html (near the bottom) for details.

@joshsusser

Looks like I lost a change when packaging this patch. Fixed in PR 8431.

@rubys

Another problem I noticed today (I'm guessing that it is related to this change, but I haven't done a bisect to prove it yet), it looks like this change makes db:rollback less useful. Compare:

http://intertwingly.net/projects/AWDwR4/checkdepot/section-10.1.html
http://intertwingly.net/projects/AWDwR4/checkdepot-32/section-10.1.html

In particular, look for rake db:rollback => Migrations are pending; run 'rake db:migrate RAILS_ENV=development' to resolve this issue.

@jonleighton
Collaborator

Great work Josh. This also opens up the possibility of making rake db:migrate:down not need the VERSION parameter. Maybe you fancy working on that :)

@jonleighton
Collaborator

heh, @fxn has actually just pointed out to me that we already have db:rollback which does this. please ignore me :)

Please sign in to comment.
Something went wrong with that request. Please try again.