mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Added change_table for migrations (Jeff Dean) [#71 state:resolved]
This commit is contained in:
parent
64092de257
commit
96980bd561
7 changed files with 507 additions and 9 deletions
|
@ -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 => 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 "can't dup NilClass" error (Steven Soroka) [#85]
|
||||
|
||||
* Added block-setting of attributes for Base.create like Base.new already has (Adam Meehan) [#39]
|
||||
|
|
|
@ -164,6 +164,28 @@ A short rundown of the major features:
|
|||
ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
|
||||
|
||||
|
||||
* Database agnostic schema management with Migrations
|
||||
|
||||
class AddSystemSettings < 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 => "notice", :label => "Use notice?", :value => 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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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 => true)
|
||||
# ====== Creating a named index
|
||||
# t.index([:branch_id, :party_id], :unique => true, :name => '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 => 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 => :branch_id
|
||||
# Remove the index named accounts_branch_id_party_id_index in the accounts table.
|
||||
# t.remove_index :column => [:branch_id, :party_id]
|
||||
# Remove the index named by_branch_party in the accounts table.
|
||||
# t.remove_index :name => :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. <tt>reference</tt>,
|
||||
# <tt>references</tt> and <tt>belongs_to</tt> are all acceptable
|
||||
# ===== Example
|
||||
# t.references(:goat)
|
||||
# t.references(:goat, :polymorphic => 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, "#{col}_id", :integer, options)
|
||||
@base.add_column(@table_name, "#{col}_type", :string, polymorphic.is_a?(Hash) ? polymorphic : options) unless polymorphic.nil?
|
||||
end
|
||||
end
|
||||
alias :belongs_to :references
|
||||
|
||||
# Adds a reference. Optionally removes a +type+ column. <tt>remove_reference</tt>,
|
||||
# <tt>remove_references</tt> and <tt>remove_belongs_to</tt> are all acceptable
|
||||
# ===== Example
|
||||
# t.remove_reference(:goat)
|
||||
# t.remove_reference(:goat, :polymorphic => 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, "#{col}_id")
|
||||
@base.remove_column(@table_name, "#{col}_type") 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 <<-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
|
||||
|
|
|
@ -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 => 60
|
||||
# # Other column alterations here
|
||||
# end
|
||||
#
|
||||
# ===== Examples
|
||||
# ====== Add a column
|
||||
# change_table(:suppliers) do |t|
|
||||
# t.column :name, :string, :limit => 60
|
||||
# end
|
||||
#
|
||||
# ====== Add 2 integer columns
|
||||
# change_table(:suppliers) do |t|
|
||||
# t.integer :width, :height, :null => false, :default => 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 <tt>company_id(integer)</tt> column
|
||||
#
|
||||
# ====== Add a polymorphic foreign key column
|
||||
# change_table(:suppliers) do |t|
|
||||
# t.belongs_to :company, :polymorphic => true
|
||||
# end
|
||||
#
|
||||
# Creates <tt>company_type(varchar)</tt> and <tt>company_id(integer)</tt> 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,12 +185,16 @@ 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)
|
||||
# remove_columns(:suppliers, :qualification, :experience)
|
||||
def remove_column(table_name, *column_names)
|
||||
column_names.flatten.each do |column_name|
|
||||
execute "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_name(column_name)}"
|
||||
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.
|
||||
|
|
|
@ -219,11 +219,14 @@ module ActiveRecord
|
|||
execute "VACUUM"
|
||||
end
|
||||
|
||||
def remove_column(table_name, column_name) #:nodoc:
|
||||
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|
|
||||
|
|
|
@ -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 => 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 < ActiveRecord::TestCase
|
||||
def setup
|
||||
@connection = Person.connection
|
||||
@connection.create_table :delete_me, :force => 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 => 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 => 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 => false})
|
||||
@connection.expects(:add_column).with(:delete_me, 'taggable_id', :integer, {:null => false})
|
||||
t.references :taggable, :polymorphic => true, :null => 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 => true, :null => 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)
|
||||
"character varying(255)"
|
||||
else
|
||||
'varchar(255)'
|
||||
end
|
||||
end
|
||||
|
||||
def integer_column
|
||||
if current_adapter?(:SQLite3Adapter) || current_adapter?(:SQLiteAdapter) || current_adapter?(:PostgreSQLAdapter)
|
||||
"integer"
|
||||
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 => false})
|
||||
t.column :bar, :integer, :null => 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 => true})
|
||||
t.index :bar, :unique => 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 => true})
|
||||
t.change :bar, :string, :null => 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 => true})
|
||||
t.remove_index :unique => 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
|
||||
|
|
Loading…
Reference in a new issue