It's like the datamapper "rake db:autoupgrade".
Write this in your migration OR initializer of your plugin/gem
if the plugin/gem require a table with some columns to exist.
ActiveRecord::Migration.upgrade_table :products do |t|
t.string :code, :null => false
t.string :name, :limit => 200
t.body :text
t.timestamps
end
In this example, the products table won't be dropped if there is
existing one in database,
If there are no "name" column, It will only add that column to the
table.
If it has the exactly the same thing in the database, it will do
nothing.
If there are more interest on this, I will continue to make a
patch for this, Else I'll just paste the code here:
require 'active_record/connection_adapters/abstract/schema_definitions'
require 'active_record/connection_adapters/abstract/schema_statements'
module ActiveRecord
module ConnectionAdapters # :nodoc:
module SchemaStatements
class SafeTable < Table
def initialize(table_name, base)
super(table_name, base)
@base.create_table(@table_name) unless @base.table_exists?(@table_name)
end
def column(column_name, type, options = {})
return super(column_name, type, options) unless @base.column_exists?(@table_name, column_name)
changed_properties = changed_properties(column_name, options)
return if changed_properties.empty?
change(column_name, type, options)
change_default(column_name, column_default) if changed_properties.include?(:default)
end
def timestamps
return super unless @base.column_exists?(@table_name, :created_at)
end
# Adds a column or columns of a specified type
# ===== Examples
# 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, __FILE__, __LINE__ + 1
def #{column_type}(*args) # def string(*args)
options = args.extract_options! # options = args.extract_options!
column_names = args # column_names = args
#
column_names.each do |name| # column_names.each do |name|
column = ColumnDefinition.new(@base, name, '#{column_type}') # column = ColumnDefinition.new(@base, name, 'string')
if options[:limit] # if options[:limit]
column.limit = options[:limit] # column.limit = options[:limit]
elsif native['#{column_type}'.to_sym].is_a?(Hash) # elsif native['string'.to_sym].is_a?(Hash)
column.limit = native['#{column_type}'.to_sym][:limit] # column.limit = native['string'.to_sym][:limit]
end # end
column.precision = options[:precision] # column.precision = options[:precision]
column.scale = options[:scale] # column.scale = options[:scale]
column.default = options[:default] # column.default = options[:default]
column.null = options[:null] # column.null = options[:null]
self.column(name, column.sql_type, options) # self.column(name, column.sql_type, options)
end # end
end # end
EOV
end
private
def changed_properties(column_name, new_options={})
original_column = @base.columns(@table_name).detect {|c| c.name.to_s == column_name.to_s }
[:default, :limit, :null, :precision, :scale].select do |attr|
original_value = original_column.send(attr)
new_value = new_options[attr]
new_value && new_value.to_s != original_value.to_s
end
end
end
def upgrade_table(table_name)
yield SafeTable.new(table_name, self)
end
end # SchemaStatements
end # ConnectionAdapters
end # ActiveRecord