Permalink
Browse files

Added change_table for migrations (Jeff Dean) [#71 state:resolved]

  • Loading branch information...
dhh committed May 3, 2008
1 parent 64092de commit 96980bd561d79824b6cb6efbcbecdcbf8785d452
View
@@ -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]
View
@@ -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 @@ 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
@@ -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')
@@ -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
@@ -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|
Oops, something went wrong.

2 comments on commit 96980bd

@thetamind

This comment has been minimized.

Show comment
Hide comment
@thetamind

thetamind May 5, 2008

Contributor

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.

Contributor

thetamind replied May 5, 2008

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

This comment has been minimized.

Show comment
Hide comment
@jerome

jerome May 10, 2008

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

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.