Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Sugared up migrations with even more bling #1609 [Tobias Luekte]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1697 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit f1880cac5862172608ff26d1178a31c05b904d77 1 parent 2418a68
@dhh dhh authored
View
96 activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -366,11 +366,17 @@ def initialize_schema_information
# Schema has been intialized
end
end
-
- def create_table(name, options = "")
- execute "CREATE TABLE #{name} (id #{native_database_types[:primary_key]}) #{options}"
- table_definition = yield TableDefinition.new
- table_definition.columns.each { |column_name, type, options| add_column(name, column_name, type, options) }
+
+ def create_table(name, options = {})
+ table_definition = TableDefinition.new(self)
+ table_definition.primary_key(options[:primary_key] || "id") unless options[:id] == false
+
+ yield table_definition
+ create_sql = "CREATE TABLE #{name} ("
+ create_sql << table_definition.to_sql
+ create_sql << ") #{options[:options]}"
+
+ execute create_sql
end
def drop_table(name)
@@ -379,28 +385,52 @@ def drop_table(name)
def add_column(table_name, column_name, type, options = {})
native_type = native_database_types[type]
- add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type)}"
- add_column_sql << " DEFAULT '#{options[:default]}'" if options[:default]
+ add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type, options[:limit])}"
+ add_column_options!(add_column_sql, options)
execute(add_column_sql)
end
-
+
def remove_column(table_name, column_name)
execute "ALTER TABLE #{table_name} DROP #{column_name}"
+ end
+
+ def change_column(table_name, column_name, type, options = {})
+ raise NotImplementedError, "change_column is not implemented"
end
def supports_migrations?
false
end
+ def rename_column(table_name, column_name, new_column_name)
+ raise NotImplementedError, "rename_column is not implemented"
+ end
+
+ def add_index(table_name, column_name, index_type = '')
+ execute "CREATE #{index_type} INDEX #{table_name}_#{column_name.to_a.first}_index ON #{table_name} (#{column_name.to_a.join(", ")})"
+ end
+
+ def remove_index(table_name, column_name)
+ execute "DROP INDEX #{table_name}_#{column_name}_index ON #{table_name}"
+ end
+
+ def supports_migrations?
+ false
+ end
+
+ def native_database_types
+ {}
+ end
+
+ def type_to_sql(type, limit = nil)
+ native = native_database_types[type]
+ limit ||= native[:limit]
+ column_type_sql = native[:name]
+ column_type_sql << "(#{limit})" if limit
+ column_type_sql
+ end
- protected
- def type_to_sql(type)
- native = native_database_types[type]
- column_type_sql = native[:name]
- column_type_sql << "(#{native[:limit]})" if native[:limit]
- column_type_sql
- end
-
+ protected
def log(sql, name)
begin
if block_given?
@@ -450,19 +480,47 @@ def format_log_entry(message, dump = nil)
"%s %s" % [message, dump]
end
end
- end
+
+ def add_column_options!(sql, options)
+ sql << " DEFAULT '#{options[:default]}'" if options[:default]
+ end
+ end
class TableDefinition
attr_accessor :columns
- def initialize
+ def initialize(base)
@columns = []
+ @base = base
+ end
+
+ def primary_key(name)
+ @columns << "#{name} #{native[:primary_key]}"
+ self
end
def column(name, type, options = {})
- @columns << [ name, type, options ]
+ limit = options[:limit] || native[type.to_sym][:limit]
+
+ column_sql = "#{name} #{type_to_sql(type.to_sym, options[:limit])}"
+ column_sql << " DEFAULT '#{options[:default]}'" if options[:default]
+ @columns << column_sql
self
end
+
+ def to_sql
+ @columns.join(", ")
+ end
+
+ private
+
+ def type_to_sql(name, limit)
+ @base.type_to_sql(name, limit)
+ end
+
+ def native
+ @base.native_database_types
+ end
end
end
end
View
15 activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
@@ -199,8 +199,19 @@ def create_database(name)
execute "CREATE DATABASE #{name}"
end
- def create_table(name)
- super(name, "ENGINE=InnoDB")
+ def change_column(table_name, column_name, type, options = {})
+ change_column_sql = "ALTER TABLE #{table_name} MODIFY #{column_name} #{type}"
+ add_column_options!(change_column_sql, options)
+ execute(change_column_sql)
+ end
+
+ def rename_column(table_name, column_name, new_column_name)
+ current_type = select_one("SHOW COLUMNS FROM #{table_name} LIKE '#{column_name}'")["Type"]
+ execute "ALTER TABLE #{table_name} CHANGE #{column_name} #{new_column_name} #{current_type}"
+ end
+
+ def create_table(name, options = {})
+ super(name, {:options => "ENGINE=InnoDB"}.merge(options))
end
private
View
18 activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -60,7 +60,6 @@ module ConnectionAdapters
# * <tt>:encoding</tt> -- An optional client encoding that is using in a SET client_encoding TO <encoding> call on connection.
# * <tt>:min_messages</tt> -- An optional client min messages that is using in a SET client_min_messages TO <min_messages> call on connection.
class PostgreSQLAdapter < AbstractAdapter
-
def native_database_types
{
:primary_key => "serial primary key",
@@ -132,7 +131,7 @@ def quote_column_name(name)
%("#{name}")
end
- def adapter_name()
+ def adapter_name
'PostgreSQL'
end
@@ -150,8 +149,21 @@ def schema_search_path=(schema_csv)
def schema_search_path
@schema_search_path ||= query('SHOW search_path')[0][0]
end
+
+ def change_column(table_name, column_name, type, options = {})
+ change_column_sql = "ALTER TABLE #{table_name} ALTER COLUMN #{column_name} TYPE #{type}"
+ add_column_options!(change_column_sql, options)
+ execute(change_column_sql)
+ end
+
+ def rename_column(table_name, column_name, new_column_name)
+ execute "ALTER TABLE #{table_name} RENAME COLUMN #{column_name} TO #{new_column_name}"
+ end
-
+ def remove_index(table_name, column_name)
+ execute "DROP INDEX #{table_name}_#{column_name}_index"
+ end
+
private
BYTEA_COLUMN_TYPE_OID = 17
View
17 activerecord/lib/active_record/migration.rb
@@ -51,21 +51,32 @@ class IrreversibleMigration < ActiveRecordError#:nodoc:
#
# == Available transformations
#
- # * <tt>create_table(name, options = "")</tt> Creates a table called +name+ and makes the table object available to a block
- # that can then add columns to it, following the same format as add_column. See example above. The options string is for
+ # * <tt>create_table(name, options)</tt> Creates a table called +name+ and makes the table object available to a block
+ # that can then add columns to it, following the same format as add_column. See example above. The options hash is for
# fragments like "DEFAULT CHARSET=UTF-8" that are appended to the create table definition.
# * <tt>drop_table(name)</tt>: Drops the table called +name+.
- # * <tt>add_column(table_name, column_name, type, options = {})</tt>: Adds a new column to the table called +table_name+
+ # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
# named +column_name+ specified to be one of the following types:
# :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean. A default value can be specified
# by passing an +options+ hash like { :default => 11 }.
+ # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
+ # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
+ # parameters as add_column.
# * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
+ # * <tt>add_index(table_name, column_name)</tt>: Add a new index with the name of the column on the column.
+ # * <tt>remove_index(table_name, column_name)</tt>: Remove the index called the same as the column.
#
# == Irreversible transformations
#
# Some transformations are destructive in a manner that cannot be reversed. Migrations of that kind should raise
# an <tt>IrreversibleMigration</tt> exception in their +down+ method.
#
+ # == Running migrations from within Rails
+ #
+ # The Rails package has support for migrations with the <tt>script/generate migration my_new_migration</tt> command and
+ # with the <tt>rake migrate</tt> command that'll run all the pending migrations. It'll even create the needed schema_info
+ # table automatically if it's missing.
+ #
# == Database support
#
# Migrations are currently only supported in MySQL and PostgreSQL.
View
12 activerecord/test/fixtures/migrations/3_innocent_jointable.rb
@@ -0,0 +1,12 @@
+class InnocentJointable < ActiveRecord::Migration
+ def self.up
+ create_table("people_reminders", :id => false) do |t|
+ t.column :reminder_id, :integer
+ t.column :person_id, :integer
+ end
+ end
+
+ def self.down
+ drop_table "people_reminders"
+ end
+end
View
63 activerecord/test/migration_test.rb
@@ -4,6 +4,7 @@
require File.dirname(__FILE__) + '/fixtures/migrations/2_we_need_reminders'
if ActiveRecord::Base.connection.supports_migrations?
+
class Reminder < ActiveRecord::Base; end
class MigrationTest < Test::Unit::TestCase
@@ -15,6 +16,7 @@ def teardown
ActiveRecord::Base.connection.update "UPDATE schema_info SET version = 0"
Reminder.connection.drop_table("reminders") rescue nil
+ Reminder.connection.drop_table("people_reminders") rescue nil
Reminder.reset_column_information
Person.connection.remove_column("people", "last_name") rescue nil
@@ -24,11 +26,21 @@ def teardown
Person.connection.remove_column("people", "birthday") rescue nil
Person.connection.remove_column("people", "favorite_day") rescue nil
Person.connection.remove_column("people", "male") rescue nil
+ Person.connection.remove_column("people", "administrator") rescue nil
Person.reset_column_information
end
+
+ def test_add_index
+ Person.connection.add_column "people", "last_name", :string
+
+ assert_nothing_raised { Person.connection.add_index("people", "last_name") }
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
+
+ assert_nothing_raised { Person.connection.add_index("people", ["last_name", "first_name"]) }
+ assert_nothing_raised { Person.connection.remove_index("people", "last_name") }
+ end
def test_native_types
-
Person.delete_all
Person.connection.add_column "people", "last_name", :string
Person.connection.add_column "people", "bio", :text
@@ -62,21 +74,54 @@ def test_add_remove_single_field
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
-
+
PeopleHaveLastNames.down
Person.reset_column_information
assert !Person.column_methods_hash.include?(:last_name)
end
+
+ def test_add_rename
+ Person.delete_all
+
+ Person.connection.add_column "people", "girlfriend", :string
+ Person.create :girlfriend => 'bobette'
+
+ begin
+ Person.connection.rename_column "people", "girlfriend", "exgirlfriend"
+
+ Person.reset_column_information
+ bob = Person.find(:first)
+
+ assert_equal "bobette", bob.exgirlfriend
+ ensure
+ Person.connection.remove_column("people", "girlfriend") rescue nil
+ Person.connection.remove_column("people", "exgirlfriend") rescue nil
+ end
+
+ end
+
+ def test_change_column
+ Person.connection.add_column "people", "bio", :string
+ assert_nothing_raised { Person.connection.change_column "people", "bio", :text }
+ end
+
+ def test_change_column_with_new_default
+ Person.connection.add_column "people", "administrator", :boolean, :default => 1
+ assert Person.new.administrator?
+
+ assert_nothing_raised { Person.connection.change_column "people", "administrator", :boolean, :default => 0 }
+ assert !Person.new.administrator?
+ end
def test_add_table
assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
-
+
WeNeedReminders.up
-
+
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.find(:first).content
-
+
WeNeedReminders.down
assert_raises(ActiveRecord::StatementInvalid) { Reminder.find(:first) }
end
@@ -87,7 +132,7 @@ def test_migrator
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
- assert_equal 2, ActiveRecord::Migrator.current_version
+ assert_equal 3, ActiveRecord::Migrator.current_version
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
@@ -117,17 +162,17 @@ def test_migrator_one_up
assert Reminder.create("content" => "hello world", "remind_at" => Time.now)
assert_equal "hello world", Reminder.find(:first).content
end
-
+
def test_migrator_one_down
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/')
-
+
ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
Person.reset_column_information
assert Person.column_methods_hash.include?(:last_name)
assert_raises(ActiveRecord::StatementInvalid) { Reminder.column_methods_hash }
end
-
+
def test_migrator_one_up_one_down
ActiveRecord::Migrator.up(File.dirname(__FILE__) + '/fixtures/migrations/', 1)
ActiveRecord::Migrator.down(File.dirname(__FILE__) + '/fixtures/migrations/', 0)
Please sign in to comment.
Something went wrong with that request. Please try again.