aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/CHANGELOG.md11
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb4
-rw-r--r--activerecord/lib/active_record/schema.rb40
-rw-r--r--activerecord/lib/active_record/schema_dumper.rb17
-rw-r--r--activerecord/lib/active_record/schema_migration.rb12
-rw-r--r--activerecord/test/cases/ar_schema_test.rb51
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb18
-rw-r--r--activerecord/test/migrations/always_safe/1001_always_safe.rb5
-rw-r--r--activerecord/test/migrations/always_safe/1002_still_safe.rb5
9 files changed, 146 insertions, 17 deletions
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index ecec187329..fdeefa4f7b 100644
--- a/activerecord/CHANGELOG.md
+++ b/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
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index 7802cb02d8..a470e8de07 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -490,8 +490,8 @@ module ActiveRecord
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.
diff --git a/activerecord/lib/active_record/schema.rb b/activerecord/lib/active_record/schema.rb
index eaa4aa7086..7ec06e699b 100644
--- a/activerecord/lib/active_record/schema.rb
+++ b/activerecord/lib/active_record/schema.rb
@@ -34,27 +34,45 @@ module ActiveRecord
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
diff --git a/activerecord/lib/active_record/schema_dumper.rb b/activerecord/lib/active_record/schema_dumper.rb
index 36bde44e7c..73c0f5b9eb 100644
--- a/activerecord/lib/active_record/schema_dumper.rb
+++ b/activerecord/lib/active_record/schema_dumper.rb
@@ -24,6 +24,7 @@ module ActiveRecord
def dump(stream)
header(stream)
+ migrations(stream)
tables(stream)
trailer(stream)
stream
@@ -44,7 +45,7 @@ module ActiveRecord
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 @@ module ActiveRecord
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|
diff --git a/activerecord/lib/active_record/schema_migration.rb b/activerecord/lib/active_record/schema_migration.rb
index fd226d5eba..4464f54145 100644
--- a/activerecord/lib/active_record/schema_migration.rb
+++ b/activerecord/lib/active_record/schema_migration.rb
@@ -53,5 +53,17 @@ module ActiveRecord
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
diff --git a/activerecord/test/cases/ar_schema_test.rb b/activerecord/test/cases/ar_schema_test.rb
index b2eac0349b..bd47ba8741 100644
--- a/activerecord/test/cases/ar_schema_test.rb
+++ b/activerecord/test/cases/ar_schema_test.rb
@@ -46,4 +46,55 @@ if ActiveRecord::Base.connection.supports_migrations?
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
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index 2f75eb0995..373463c8ab 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/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 @@ class SchemaDumperTest < ActiveRecord::TestCase
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 @@ class SchemaDumperTest < ActiveRecord::TestCase
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
diff --git a/activerecord/test/migrations/always_safe/1001_always_safe.rb b/activerecord/test/migrations/always_safe/1001_always_safe.rb
new file mode 100644
index 0000000000..454b972507
--- /dev/null
+++ b/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
diff --git a/activerecord/test/migrations/always_safe/1002_still_safe.rb b/activerecord/test/migrations/always_safe/1002_still_safe.rb
new file mode 100644
index 0000000000..7398ae27a2
--- /dev/null
+++ b/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