lib/migrant/migration_generator.rb in migrant-1.1.1 vs lib/migrant/migration_generator.rb in migrant-1.1.2

- old
+ new

@@ -1,5 +1,7 @@ +require 'erubis' + module Migrant class MigrationGenerator TABS = ' ' # Tabs to spaces * 2 NEWLINE = "\n " def run @@ -19,83 +21,56 @@ Dir["#{Rails.root.to_s}/app/models/**/*.rb"].each { |f| require(f) } ActiveRecord::Base.descendants.each do |model| next if model.schema.nil? || !model.schema.requires_migration? # Skips inherited schemas (such as models with STI) model.reset_column_information # db:migrate doesn't do this - model_schema = model.schema.column_migrations + @table_name = model.table_name if model.table_exists? # Structure ActiveRecord::Base's column information so we can compare it directly to the schema db_schema = Hash[*model.columns.collect {|c| [c.name.to_sym, Hash[*[:type, :limit].map { |type| [type, c.send(type)] }.flatten] ] }.flatten] - changes = model.schema.columns.collect do |name, data_type| - begin - [name, data_type.structure_changes_from(db_schema[name])] - rescue DataType::DangerousMigration - puts "Cannot generate migration automatically for #{model.table_name}, this would involve possible data loss on column: #{name}\nOld structure: #{db_schema[name].inspect}. New structure: #{data_type.column.inspect}\nPlease create and run this migration yourself (with the appropriate data integrity checks)" - return false + @changed_columns, @added_columns = [], [] + model.schema.columns.to_a.sort { |a,b| a.to_s <=> b.to_s }.each do |field_name, data_type| + begin + if (options = data_type.structure_changes_from(db_schema[field_name])) + if db_schema[field_name] + @changed_columns << [field_name, options, db_schema[field_name]] + else + @added_columns << [field_name, options] + end end - end.reject { |change| change[1].nil? } - next if changes.blank? - changed_fields, added_fields = [], [] + rescue DataType::DangerousMigration + puts "Cannot generate migration automatically for #{model.table_name}, this would involve possible data loss on column: #{field_name}\nOld structure: #{db_schema[field_name].inspect}. New structure: #{data_type.column.inspect}\nPlease create and run this migration yourself (with the appropriate data integrity checks)" + return false + end + end - up_code = changes.collect do |field, options| - type = options.delete(:type) - arguments = (options.blank?)? "" : ", #{options.inspect[1..-2]}" + # For adapters that can report indexes, add as necessary + if ActiveRecord::Base.connection.respond_to?(:indexes) + current_indexes = ActiveRecord::Base.connection.indexes(model.table_name).collect { |index| (index.columns.length == 1)? index.columns.first.to_sym : index.columns.collect(&:to_sym) } + @indexes = model.schema.indexes.uniq.reject { |index| current_indexes.include?(index) }.collect { |field_name| [field_name, {}] } + # Don't spam the user with indexes that columns are being created with + @new_indexes = @indexes.reject { |index, options| @changed_columns.detect { |c| c.first == index } || @added_columns.detect { |c| c.first == index } } + end - if db_schema[field] - changed_fields << field - "change_column :#{model.table_name}, :#{field}, :#{type}#{arguments}" - else - added_fields << field - "add_column :#{model.table_name}, :#{field}, :#{type}#{arguments}" - end - end.join(NEWLINE+TABS) + next if @changed_columns.empty? && @added_columns.empty? && @indexes.empty? # Nothing to do for this table - activity = 'changed_'+model.table_name+[(added_fields.blank?)? nil : '_added_'+added_fields.join('_'), (changed_fields.blank?)? nil : '_modified_'+changed_fields.join('_')].compact.join('_and_') - - down_code = changes.collect do |field, options| - if db_schema[field] - type = db_schema[field].delete(:type) - arguments = (db_schema[field].blank?)? "" : ", #{db_schema[field].inspect[1..-2]}" - "change_column :#{model.table_name}, :#{field}, :#{type}#{arguments}" - else - "remove_column :#{model.table_name}, :#{field}" - end - end.join(NEWLINE+TABS) - - # For adapters that can report indexes, add as necessary - if ActiveRecord::Base.connection.respond_to?(:indexes) - current_indexes = ActiveRecord::Base.connection.indexes(model.table_name).collect { |index| (index.columns.length == 1)? index.columns.first.to_sym : index.columns.collect(&:to_sym) } - up_code += model.schema.indexes.uniq.collect do |index| - unless current_indexes.include?(index) - NEWLINE+TABS+"add_index :#{model.table_name}, #{index.inspect}" - end - end.compact.join - end + # Example: changed_table_added_something_and_modified_something + @activity = 'changed_'+model.table_name+[['added', @added_columns], ['modified', @changed_columns], ['indexed', @new_indexes]].reject { |v| v[1].empty? }.collect { |v| "_#{v[0]}_"+v[1].collect(&:first).join('_') }.join('_and') + @activity = @activity.split('_')[0..2].join('_') if @activity.length >= 240 # Most filesystems will raise Errno::ENAMETOOLONG otherwise + + render('change_migration') else - activity = "create_#{model.table_name}" - up_code = "create_table :#{model.table_name} do |t|"+NEWLINE+model_schema.collect do |field, options| - type = options.delete(:type) - options.delete(:was) # Aliases not relevant when creating a new table - arguments = (options.blank?)? "" : ", #{options.inspect[1..-2]}" - (TABS*2)+"t.#{type} :#{field}#{arguments}" - end.join(NEWLINE)+NEWLINE+TABS+"end" + @activity = "create_#{model.table_name}" + @columns = model.schema.column_migrations + @indexes = model.schema.indexes - down_code = "drop_table :#{model.table_name}" - up_code += NEWLINE+TABS+model.schema.indexes.collect { |fields| "add_index :#{model.table_name}, #{fields.inspect}"}.join(NEWLINE+TABS) + render("create_migration") end - - # Indexes - # down_code += NEWLINE+TABS+model.schema.indexes.collect { |fields| "remove_index :#{model.table_name}, #{fields.inspect}"}.join(NEWLINE+TABS) - begin - filename = "#{migrations_path}/#{next_migration_number}_#{activity}.rb" - File.open(filename, 'w') { |migration| migration.write(migration_template(activity, up_code, down_code)) } - rescue Errno::ENAMETOOLONG - activity = activity.split('_')[0..2].join('_') - filename = "#{migrations_path}/#{next_migration_number}_#{activity}.rb" - File.open(filename, 'w') { |migration| migration.write(migration_template(activity, up_code, down_code)) } - end + + filename = "#{migrations_path}/#{next_migration_number}_#{@activity}.rb" + File.open(filename, 'w') { |migration| migration.write(@output) } puts "Wrote #{filename}..." end true end @@ -119,19 +94,11 @@ else (highest.to_i + 1).to_s end end - def migration_template(activity, up_code, down_code) - "class #{activity.camelize.gsub(/\s/, '')} < ActiveRecord::Migration - def self.up - #{up_code} - end - - def self.down - #{down_code} - end -end" + def render(template_name) + @output = Erubis::Eruby.new(File.read(File.join(File.dirname(__FILE__), "../generators/templates/#{template_name}.erb"))).result(binding) end end end