Skip to content

Commit

Permalink
Added change_table for migrations (Jeff Dean) [#71 state:resolved]
Browse files Browse the repository at this point in the history
  • Loading branch information
dhh committed May 3, 2008
1 parent 64092de commit 96980bd
Show file tree
Hide file tree
Showing 7 changed files with 507 additions and 9 deletions.
9 changes: 9 additions & 0 deletions activerecord/CHANGELOG
@@ -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]
Expand Down
22 changes: 22 additions & 0 deletions activerecord/README
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion activerecord/RUNNING_UNIT_TESTS
Expand Up @@ -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



Expand Up @@ -469,5 +469,195 @@ def native
@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
Expand Up @@ -104,6 +104,67 @@ def create_table(table_name, options = {})
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')
Expand All @@ -124,13 +185,17 @@ def add_column(table_name, column_name, type, options = {})
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 "ALTER TABLE #{quote_table_name(table_name)} DROP #{quote_column_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.
# ===== Examples
Expand Down
Expand Up @@ -219,11 +219,14 @@ def add_column(table_name, column_name, type, options = {}) #:nodoc:
execute "VACUUM"
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|
Expand Down

2 comments on commit 96980bd

@thetamind
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve contributed a patch to cleanup some of the documentation and whitespace as well as add two missing aliased methods.

http://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/108-patch-change_table-cleanup

I would appreciate feedback.

@jerome
Copy link

@jerome jerome commented on 96980bd May 10, 2008

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mmmm

Submodule path ‘vendor/rails’: checked out ‘7f4171da5e3ed5b3e038b95f8f5ae05ba6e21bef’

$ rake db:migrate
(…)
— change_table(:pages)
rake aborted!
undefined method `change_table’ for #ActiveRecord::ConnectionAdapters::SQLite3Adapter:0×2131e40

Please sign in to comment.