diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2005-07-04 18:51:02 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2005-07-04 18:51:02 +0000 |
commit | 4160b518a82bcaa84e0e3125b4947b2dc3837fa3 (patch) | |
tree | def4e797e1834c65864498509ea98edd7dad7745 /activerecord/lib | |
parent | 452442dde8e8ea5949c387ea5c78387bff330f2a (diff) | |
download | rails-4160b518a82bcaa84e0e3125b4947b2dc3837fa3.tar.gz rails-4160b518a82bcaa84e0e3125b4947b2dc3837fa3.tar.bz2 rails-4160b518a82bcaa84e0e3125b4947b2dc3837fa3.zip |
Added new Migrations framework for describing schema transformations in a way that can be easily applied across multiple databases #1604 [Tobias Luetke]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1672 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'activerecord/lib')
4 files changed, 139 insertions, 8 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb index 1137ea1011..1b5c8184ae 100755 --- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb @@ -357,9 +357,10 @@ module ActiveRecord sql << " OFFSET #{options[:offset]}" if options.has_key?(:offset) and !options[:offset].nil? end + def initialize_schema_information begin - execute "CREATE TABLE schema_info (version #{native_database_types[:integer][:name]}(#{native_database_types[:integer][:limit]}))" + execute "CREATE TABLE schema_info (version #{type_to_sql(:integer)})" insert "INSERT INTO schema_info (version) VALUES(0)" rescue ActiveRecord::StatementInvalid # Schema has been intialized @@ -378,8 +379,7 @@ module ActiveRecord 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} #{native_type[:name]}" - add_column_sql << "(#{options[:limit] || native_type[:limit]})" if options[:limit] || native_type[:limit] + add_column_sql = "ALTER TABLE #{table_name} ADD #{column_name} #{type_to_sql(type)}" add_column_sql << " DEFAULT '#{options[:default]}'" if options[:default] execute(add_column_sql) end @@ -387,9 +387,20 @@ module ActiveRecord def remove_column(table_name, column_name) execute "ALTER TABLE #{table_name} DROP #{column_name}" end + + def supports_migrations? + false + 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 + def log(sql, name) begin if block_given? @@ -439,7 +450,7 @@ module ActiveRecord "%s %s" % [message, dump] end end - end + end class TableDefinition attr_accessor :columns diff --git a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb index ad0e490194..ec0558a4d5 100755 --- a/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql_adapter.rb @@ -63,6 +63,10 @@ module ActiveRecord "Lost connection to MySQL server during query", "MySQL server has gone away" ] + + def supports_migrations? + true + end def native_database_types { @@ -89,7 +93,6 @@ module ActiveRecord 'MySQL' end - def select_all(sql, name = nil) select(sql, name) end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb index 66cbe3a58b..3d6550ea9a 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb @@ -60,6 +60,27 @@ module ActiveRecord # * <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", + :string => { :name => "character varying", :limit => 255 }, + :text => { :name => "text" }, + :integer => { :name => "integer" }, + :float => { :name => "float" }, + :datetime => { :name => "timestamp" }, + :timestamp => { :name => "timestamp" }, + :time => { :name => "timestamp" }, + :date => { :name => "date" }, + :binary => { :name => "bytea" }, + :boolean => { :name => "boolean"} + } + end + + def supports_migrations? + true + end + def select_all(sql, name = nil) select(sql, name) end diff --git a/activerecord/lib/active_record/migration.rb b/activerecord/lib/active_record/migration.rb index 7a1c47038d..cb54d2b967 100644 --- a/activerecord/lib/active_record/migration.rb +++ b/activerecord/lib/active_record/migration.rb @@ -2,7 +2,102 @@ module ActiveRecord class IrreversibleMigration < ActiveRecordError#:nodoc: end - class Migration #:nodoc: + # Migrations can manage the evolution of a schema used by several physical databases. It's a solution + # to the common problem of adding a field to make a new feature work in your local database, but being unsure of how to + # push that change to other developers and to the production server. With migrations, you can describe the transformations + # in self-contained classes that can be checked into version control systems and executed against another database that + # might be one, two, or five versions behind. + # + # Example of a simple migration: + # + # class AddSsl < ActiveRecord::Migration + # def self.up + # add_column :accounts, :ssl_enabled, :boolean, :default => 1 + # end + # + # def self.down + # remove_column :accounts, :ssl_enabled + # end + # end + # + # This migration will add a boolean flag to the accounts table and remove it again, if you're backing out of the migration. + # It shows how all migrations have two class methods +up+ and +down+ that describes the transformations required to implement + # or remove the migration. These methods can consist of both the migration specific methods, like add_column and remove_column, + # but may also contain regular Ruby code for generating data needed for the transformations. + # + # Example of a more complex migration that also needs to initialize data: + # + # class AddSystemSettings < ActiveRecord::Migration + # def self.up + # create_table :system_settings do |t| + # t.column :name, :string + # t.column :label, :string + # t.column :value, :text + # t.column :type, :string + # t.column :position, :integer + # end + # + # SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1 + # end + # + # def self.down + # drop_table :system_settings + # end + # end + # + # This migration first adds the system_settings table, then creates the very first row in it using the Active Record model + # that relies on the table. It also uses the more advanced create_table syntax where you can specify a complete table schema + # in one block call. + # + # == 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 + # 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+ + # 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>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+. + # + # == 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. + # + # == Database support + # + # Migrations are currently only supported in MySQL and PostgreSQL. + # + # == More examples + # + # Not all migrations change the schema. Some just fix the data: + # + # class RemoveEmptyTags < ActiveRecord::Migration + # def self.up + # Tag.find(:all).each { |tag| tag.destroy if tag.pages.empty? } + # end + # + # def self.down + # # not much we can do to restore deleted data + # end + # end + # + # Others remove columns when they migrate up instead of down: + # + # class RemoveUnnecessaryItemAttributes < ActiveRecord::Migration + # def self.up + # remove_column :items, :incomplete_items_count + # remove_column :items, :completed_items_count + # end + # + # def self.down + # add_column :items, :incomplete_items_count + # add_column :items, :completed_items_count + # end + # end + class Migration class << self def up() end def down() end @@ -17,11 +112,11 @@ module ActiveRecord class Migrator#:nodoc: class << self def up(migrations_path, target_version = nil) - new(:up, migrations_path, target_version).migrate + self.new(:up, migrations_path, target_version).migrate end def down(migrations_path, target_version = nil) - new(:down, migrations_path, target_version).migrate + self.new(:down, migrations_path, target_version).migrate end def current_version @@ -30,6 +125,7 @@ module ActiveRecord end def initialize(direction, migrations_path, target_version = nil) + raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations? @direction, @migrations_path, @target_version = direction, migrations_path, target_version Base.connection.initialize_schema_information end |