<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array"/>
  <modified type="array">
    <modified>
      <diff>@@ -1,5 +1,14 @@
 *SVN*
 
+* Added change_table for migrations (Jeff Dean) [#71]. Example:
+
+    change_table :videos do |t|
+      t.timestamps                          # adds created_at, updated_at
+      t.belongs_to :goat                    # add goat_id integer
+      t.string :name, :email, :limit =&gt; 20  # adds name and email both with a 20 char limit
+      t.remove :name, :email                # removes the name and email columns
+    end
+
 * Fixed has_many :through .create with no parameters caused a &quot;can't dup NilClass&quot; error (Steven Soroka) [#85]
 
 * Added block-setting of attributes for Base.create like Base.new already has (Adam Meehan) [#39]</diff>
      <filename>activerecord/CHANGELOG</filename>
    </modified>
    <modified>
      <diff>@@ -164,6 +164,28 @@ A short rundown of the major features:
     ActiveRecord::Base.logger = Log4r::Logger.new(&quot;Application Log&quot;)
 
 
+* Database agnostic schema management with Migrations
+
+    class AddSystemSettings &lt; ActiveRecord::Migration
+      def self.up
+        create_table :system_settings do |t|
+          t.string  :name
+          t.string  :label
+          t.text  :value
+          t.string  :type
+          t.integer  :position
+        end
+
+        SystemSetting.create :name =&gt; &quot;notice&quot;, :label =&gt; &quot;Use notice?&quot;, :value =&gt; 1
+      end
+
+      def self.down
+        drop_table :system_settings
+      end
+    end
+
+  {Learn more}[link:classes/ActiveRecord/Migration.html]
+
 == Simple example (1/2): Defining tables and classes (using MySQL)
 
 Data definitions are specified only in the database. Active Record queries the database for </diff>
      <filename>activerecord/README</filename>
    </modified>
    <modified>
      <diff>@@ -27,7 +27,10 @@ you can do so with:
 
    rake test_mysql TEST=test/cases/base_test.rb
    
-That'll run the base suite using the MySQL-Ruby adapter.
+That'll run the base suite using the MySQL-Ruby adapter.  Some tests rely on the schema
+being initialized - you can initialize the schema with:
+
+  rake test_mysql TEST=test/cases/aaa_create_tables_test.rb
 
 
 </diff>
      <filename>activerecord/RUNNING_UNIT_TESTS</filename>
    </modified>
    <modified>
      <diff>@@ -469,5 +469,195 @@ module ActiveRecord
           @base.native_database_types
         end
     end
+
+    # Represents a SQL table in an abstract way for updating a table.
+    # Also see TableDefinition and SchemaStatements#create_table
+    #
+    # Available transformations are:
+    #
+    #   change_table :table do |t|
+    #     t.column
+    #     t.index
+    #     t.timestamps
+    #     t.change
+    #     t.change_default
+    #     t.rename
+    #     t.references
+    #     t.belongs_to
+    #     t.string
+    #     t.text
+    #     t.integer
+    #     t.float
+    #     t.decimal
+    #     t.datetime
+    #     t.timestamp
+    #     t.time
+    #     t.date
+    #     t.binary
+    #     t.boolean
+    #     t.remove
+    #     t.remove_references
+    #     t.remove_belongs_to
+    #     t.remove_index
+    #     t.remove_timestamps
+    #   end
+    #
+    class Table
+      def initialize(table_name, base)
+        @table_name = table_name
+        @base = base
+      end
+
+      # Adds a new column to the named table.
+      # See TableDefinition#column for details of the options you can use.
+      # ===== Examples
+      # ====== Creating a simple columns
+      #  t.column(:name, :string)
+      def column(column_name, type, options = {})
+        @base.add_column(@table_name, column_name, type, options)
+      end
+
+      # Adds a new index to the table.  +column_name+ can be a single Symbol, or
+      # an Array of Symbols.  See SchemaStatements#add_index
+      #
+      # ===== Examples
+      # ====== Creating a simple index
+      #  t.index(:name)
+      # ====== Creating a unique index
+      #  t.index([:branch_id, :party_id], :unique =&gt; true)
+      # ====== Creating a named index
+      #  t.index([:branch_id, :party_id], :unique =&gt; true, :name =&gt; 'by_branch_party')
+      def index(column_name, options = {})
+        @base.add_index(@table_name, column_name, options)
+      end
+
+      # Adds timestamps (created_at and updated_at) columns to the table.  See SchemaStatements#timestamps
+      # ===== Examples
+      #  t.timestamps
+      def timestamps
+        @base.add_timestamps(@table_name)
+      end
+
+      # Changes the column's definition according to the new options.
+      # See TableDefinition#column for details of the options you can use.
+      # ===== Examples
+      #  t.change(:name, :string, :limit =&gt; 80)
+      #  t.change(:description, :text)
+      def change(column_name, type, options = {})
+        @base.change_column(@table_name, column_name, type, options)
+      end
+
+      # Sets a new default value for a column.  See
+      # ===== Examples
+      #  t.change_default(:qualification, 'new')
+      #  t.change_default(:authorized, 1)
+      def change_default(column_name, default)
+        @base.change_column_default(@table_name, column_name, default)
+      end
+
+      # Removes the column(s) from the table definition.
+      # ===== Examples
+      #  t.remove(:qualification)
+      #  t.remove(:qualification, :experience)
+      #  t.removes(:qualification, :experience)
+      def remove(*column_names)
+        @base.remove_column(@table_name, column_names)
+      end
+
+      # Remove the given index from the table.
+      #
+      # Remove the suppliers_name_index in the suppliers table.
+      #   t.remove_index :name
+      # Remove the index named accounts_branch_id_index in the accounts table.
+      #   t.remove_index :column =&gt; :branch_id
+      # Remove the index named accounts_branch_id_party_id_index in the accounts table.
+      #   t.remove_index :column =&gt; [:branch_id, :party_id]
+      # Remove the index named by_branch_party in the accounts table.
+      #   t.remove_index :name =&gt; :by_branch_party
+      def remove_index(options = {})
+        @base.remove_index(@table_name, options)
+      end
+
+      # Removes the timestamp columns (created_at and updated_at) from the table.
+      # ===== Examples
+      #  t.remove_timestamps
+      def remove_timestamps
+        @base.remove_timestamps(@table_name)
+      end
+
+      # Renames a column.
+      # ===== Example
+      #  t.rename(:description, :name)
+      def rename(column_name, new_column_name)
+        @base.rename_column(@table_name, column_name, new_column_name)
+      end
+
+      # Adds a reference.  Optionally adds a +type+ column.  &lt;tt&gt;reference&lt;/tt&gt;,
+      # &lt;tt&gt;references&lt;/tt&gt; and &lt;tt&gt;belongs_to&lt;/tt&gt; are all acceptable
+      # ===== Example
+      #  t.references(:goat)
+      #  t.references(:goat, :polymorphic =&gt; true)
+      #  t.references(:goat)
+      #  t.belongs_to(:goat)
+      def references(*args)
+        options = args.extract_options!
+        polymorphic = options.delete(:polymorphic)
+        args.each do |col|
+          @base.add_column(@table_name, &quot;#{col}_id&quot;, :integer, options)
+          @base.add_column(@table_name, &quot;#{col}_type&quot;, :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
+        end
+      end
+      alias :belongs_to :references
+
+      # Adds a reference.  Optionally removes a +type+ column.  &lt;tt&gt;remove_reference&lt;/tt&gt;,
+      # &lt;tt&gt;remove_references&lt;/tt&gt; and &lt;tt&gt;remove_belongs_to&lt;/tt&gt; are all acceptable
+      # ===== Example
+      #  t.remove_reference(:goat)
+      #  t.remove_reference(:goat, :polymorphic =&gt; true)
+      #  t.remove_references(:goat)
+      #  t.remove_belongs_to(:goat)
+      def remove_references(*args)
+        options = args.extract_options!
+        polymorphic = options.delete(:polymorphic)
+        args.each do |col|
+          @base.remove_column(@table_name, &quot;#{col}_id&quot;)
+          @base.remove_column(@table_name, &quot;#{col}_type&quot;) unless polymorphic.nil?
+        end
+      end
+      alias :remove_belongs_to  :remove_references
+
+      # Adds a column or columns of a specified type
+      # ===== Example
+      #  t.string(:goat)
+      #  t.string(:goat, :sheep)
+      %w( string text integer float decimal datetime timestamp time date binary boolean ).each do |column_type|
+        class_eval &lt;&lt;-EOV
+          def #{column_type}(*args)
+            options = args.extract_options!
+            column_names = args
+
+            column_names.each do |name|
+              column = ColumnDefinition.new(@base, name, '#{column_type}')
+              if options[:limit]
+                column.limit = options[:limit]
+              elsif native['#{column_type}'.to_sym].is_a?(Hash)
+                column.limit = native['#{column_type}'.to_sym][:limit]
+              end
+              column.precision = options[:precision]
+              column.scale = options[:scale]
+              column.default = options[:default]
+              column.null = options[:null]
+              @base.add_column(@table_name, name, column.sql_type, options)
+            end
+          end
+        EOV
+      end
+
+      private
+        def native
+          @base.native_database_types
+        end
+    end
+
   end
 end</diff>
      <filename>activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb</filename>
    </modified>
    <modified>
      <diff>@@ -104,6 +104,67 @@ module ActiveRecord
         execute create_sql
       end
 
+      # A block for changing columns in +table+.
+      #
+      # === Example
+      #  # change_table() yields a Table instance
+      #  change_table(:suppliers) do |t|
+      #    t.column :name, :string, :limit =&gt; 60
+      #    # Other column alterations here
+      #  end
+      #
+      # ===== Examples
+      # ====== Add a column
+      #  change_table(:suppliers) do |t|
+      #    t.column :name, :string, :limit =&gt; 60
+      #  end
+      #
+      # ====== Add 2 integer columns
+      #  change_table(:suppliers) do |t|
+      #    t.integer :width, :height, :null =&gt; false, :default =&gt; 0
+      #  end
+      #
+      # ====== Add created_at/updated_at columns
+      #  change_table(:suppliers) do |t|
+      #    t.timestamps
+      #  end
+      #
+      # ====== Add a foreign key column
+      #  change_table(:suppliers) do |t|
+      #    t.references :company
+      #  end
+      #
+      # Creates a &lt;tt&gt;company_id(integer)&lt;/tt&gt; column
+      #
+      # ====== Add a polymorphic foreign key column
+      #  change_table(:suppliers) do |t|
+      #    t.belongs_to :company, :polymorphic =&gt; true
+      #  end
+      #
+      # Creates &lt;tt&gt;company_type(varchar)&lt;/tt&gt; and &lt;tt&gt;company_id(integer)&lt;/tt&gt; columns
+      #
+      # ====== Remove a column
+      #  change_table(:suppliers) do |t|
+      #    t.remove :company
+      #  end
+      #
+      # ====== Remove a column
+      #  change_table(:suppliers) do |t|
+      #    t.remove :company_id
+      #    t.remove :width, :height
+      #  end
+      #
+      # ====== Remove an index
+      #  change_table(:suppliers) do |t|
+      #    t.remove_index :company_id
+      #  end
+      #
+      # See also Table for details on
+      # all of the various column transformation
+      def change_table(table_name)
+        yield Table.new(table_name, self)
+      end
+      
       # Renames a table.
       # ===== Example
       #  rename_table('octopuses', 'octopi')
@@ -124,13 +185,17 @@ module ActiveRecord
         execute(add_column_sql)
       end
 
-      # Removes the column from the table definition.
+      # Removes the column(s) from the table definition.
       # ===== Examples
       #  remove_column(:suppliers, :qualification)
-      def remove_column(table_name, column_name)
-        execute &quot;ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}&quot;
+      #  remove_columns(:suppliers, :qualification, :experience)
+      def remove_column(table_name, *column_names)
+        column_names.flatten.each do |column_name|
+          execute &quot;ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}&quot;
+        end
       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.
       # ===== Examples</diff>
      <filename>activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb</filename>
    </modified>
    <modified>
      <diff>@@ -219,11 +219,14 @@ module ActiveRecord
         execute &quot;VACUUM&quot;
       end
 
-      def remove_column(table_name, column_name) #:nodoc:
-        alter_table(table_name) do |definition|
-          definition.columns.delete(definition[column_name])
+      def remove_column(table_name, *column_names) #:nodoc:
+        column_names.flatten.each do |column_name|
+          alter_table(table_name) do |definition|
+            definition.columns.delete(definition[column_name])
+          end
         end
       end
+      alias :remove_columns :remove_column
 
       def change_column_default(table_name, column_name, default) #:nodoc:
         alter_table(table_name) do |definition|</diff>
      <filename>activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb</filename>
    </modified>
    <modified>
      <diff>@@ -1077,7 +1077,7 @@ if ActiveRecord::Base.connection.supports_migrations?
 
       protected
       def with_new_table
-        Person.connection.create_table :delete_me do |t|
+        Person.connection.create_table :delete_me, :force =&gt; true do |t|
           yield t
         end
       ensure
@@ -1086,4 +1086,210 @@ if ActiveRecord::Base.connection.supports_migrations?
 
     end # SexyMigrationsTest
   end # uses_mocha
+
+  uses_mocha 'ChangeTable migration tests' do
+    class ChangeTableMigrationsTest &lt; ActiveRecord::TestCase
+      def setup
+        @connection = Person.connection
+        @connection.create_table :delete_me, :force =&gt; true do |t|
+        end
+      end
+
+      def teardown
+        Person.connection.drop_table :delete_me rescue nil
+      end
+
+      def test_references_column_type_adds_id
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
+          t.references :customer
+        end
+      end
+
+      def test_remove_references_column_type_removes_id
+        with_change_table do |t|
+          @connection.expects(:remove_column).with(:delete_me, 'customer_id')
+          t.remove_references :customer
+        end
+      end
+
+      def test_add_belongs_to_works_like_add_references
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, 'customer_id', :integer, {})
+          t.belongs_to :customer
+        end
+      end
+
+      def test_remove_belongs_to_works_like_remove_references
+        with_change_table do |t|
+          @connection.expects(:remove_column).with(:delete_me, 'customer_id')
+          t.remove_belongs_to :customer
+        end
+      end
+
+      def test_references_column_type_with_polymorphic_adds_type
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {})
+          @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {})
+          t.references :taggable, :polymorphic =&gt; true
+        end
+      end
+
+      def test_remove_references_column_type_with_polymorphic_removes_type
+        with_change_table do |t|
+          @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
+          @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
+          t.remove_references :taggable, :polymorphic =&gt; true
+        end
+      end
+
+      def test_references_column_type_with_polymorphic_and_options_null_is_false_adds_table_flag
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, 'taggable_type', :string, {:null =&gt; false})
+          @connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null =&gt; false})
+          t.references :taggable, :polymorphic =&gt; true, :null =&gt; false
+        end
+      end
+
+      def test_remove_references_column_type_with_polymorphic_and_options_null_is_false_removes_table_flag
+        with_change_table do |t|
+          @connection.expects(:remove_column).with(:delete_me, 'taggable_type')
+          @connection.expects(:remove_column).with(:delete_me, 'taggable_id')
+          t.remove_references :taggable, :polymorphic =&gt; true, :null =&gt; false
+        end
+      end
+
+      def test_timestamps_creates_updated_at_and_created_at
+        with_change_table do |t|
+          @connection.expects(:add_timestamps).with(:delete_me)
+          t.timestamps
+        end
+      end
+
+      def test_remove_timestamps_creates_updated_at_and_created_at
+        with_change_table do |t|
+          @connection.expects(:remove_timestamps).with(:delete_me)
+          t.remove_timestamps
+        end
+      end
+
+      def string_column
+        if current_adapter?(:PostgreSQLAdapter)
+          &quot;character varying(255)&quot;
+        else
+          'varchar(255)'
+        end
+      end
+
+      def integer_column
+        if current_adapter?(:SQLite3Adapter) || current_adapter?(:SQLiteAdapter) || current_adapter?(:PostgreSQLAdapter)
+          &quot;integer&quot;
+        else
+          'int(11)'
+        end
+      end
+
+      def test_integer_creates_integer_column
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, :foo, integer_column, {})
+          @connection.expects(:add_column).with(:delete_me, :bar, integer_column, {})
+          t.integer :foo, :bar
+        end
+      end
+
+      def test_string_creates_string_column
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, :foo, string_column, {})
+          @connection.expects(:add_column).with(:delete_me, :bar, string_column, {})
+          t.string :foo, :bar
+        end
+      end
+
+      def test_column_creates_column
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, :bar, :integer, {})
+          t.column :bar, :integer
+        end
+      end
+
+      def test_column_creates_column_with_options
+        with_change_table do |t|
+          @connection.expects(:add_column).with(:delete_me, :bar, :integer, {:null =&gt; false})
+          t.column :bar, :integer, :null =&gt; false
+        end
+      end
+
+      def test_index_creates_index
+        with_change_table do |t|
+          @connection.expects(:add_index).with(:delete_me, :bar, {})
+          t.index :bar
+        end
+      end
+
+      def test_index_creates_index_with_options
+        with_change_table do |t|
+          @connection.expects(:add_index).with(:delete_me, :bar, {:unique =&gt; true})
+          t.index :bar, :unique =&gt; true
+        end
+      end
+
+      def test_change_changes_column
+        with_change_table do |t|
+          @connection.expects(:change_column).with(:delete_me, :bar, :string, {})
+          t.change :bar, :string
+        end
+      end
+
+      def test_change_changes_column_with_options
+        with_change_table do |t|
+          @connection.expects(:change_column).with(:delete_me, :bar, :string, {:null =&gt; true})
+          t.change :bar, :string, :null =&gt; true
+        end
+      end
+
+      def test_change_default_changes_column
+        with_change_table do |t|
+          @connection.expects(:change_column_default).with(:delete_me, :bar, :string)
+          t.change_default :bar, :string
+        end
+      end
+
+      def test_remove_drops_single_column
+        with_change_table do |t|
+          @connection.expects(:remove_column).with(:delete_me, [:bar])
+          t.remove :bar
+        end
+      end
+
+      def test_remove_drops_multiple_columns
+        with_change_table do |t|
+          @connection.expects(:remove_column).with(:delete_me, [:bar, :baz])
+          t.remove :bar, :baz
+        end
+      end
+
+      def test_remove_index_removes_index_with_options
+        with_change_table do |t|
+          @connection.expects(:remove_index).with(:delete_me, {:unique =&gt; true})
+          t.remove_index :unique =&gt; true
+        end
+      end
+
+      def test_rename_renames_column
+        with_change_table do |t|
+          @connection.expects(:rename_column).with(:delete_me, :bar, :baz)
+          t.rename :bar, :baz
+        end
+      end
+
+      protected
+      def with_change_table
+        Person.connection.change_table :delete_me do |t|
+          yield t
+        end
+      end
+
+    end # ChangeTable test
+  end # uses_mocha
+
 end</diff>
      <filename>activerecord/test/cases/migration_test.rb</filename>
    </modified>
  </modified>
  <removed type="array"/>
  <parents type="array">
    <parent>
      <id>64092de25727c1943807bf5345107d90428135a0</id>
    </parent>
  </parents>
  <author>
    <name>David Heinemeier Hansson</name>
    <email>david@loudthinking.com</email>
  </author>
  <url>http://github.com/rails/rails/commit/96980bd561d79824b6cb6efbcbecdcbf8785d452</url>
  <id>96980bd561d79824b6cb6efbcbecdcbf8785d452</id>
  <committed-date>2008-05-03T09:29:47-07:00</committed-date>
  <authored-date>2008-05-03T09:29:47-07:00</authored-date>
  <message>Added change_table for migrations (Jeff Dean) [#71 state:resolved]</message>
  <tree>66c4d506c883dbebf628c7bed020b704980d6729</tree>
  <committer>
    <name>David Heinemeier Hansson</name>
    <email>david@loudthinking.com</email>
  </committer>
</commit>
