require 'rails/generators/migration' require 'rails/generators/active_record' require 'generators/hobo_support/thor_shell' module Hobo class MigrationGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) argument :name, :type => :string, :optional => true include Rails::Generators::Migration include Generators::HoboSupport::ThorShell # the Rails::Generators::Migration.next_migration_number gives a NotImplementedError # in Rails 3.0.0.beta4, so we need to implement the logic of ActiveRecord. # For other ORMs we will wait for the rails implementation # see http://groups.google.com/group/rubyonrails-talk/browse_thread/thread/a507ce419076cda2 def self.next_migration_number(dirname) ActiveRecord::Generators::Base.next_migration_number dirname end def self.banner "rails generate hobo:migration #{self.arguments.map(&:usage).join(' ')} [options]" end class_option :drop, :aliases => '-d', :type => :boolean, :desc => "Don't prompt with 'drop or rename' - just drop everything" class_option :default_name, :aliases => '-n', :type => :boolean, :desc => "Don't prompt for a migration name - just pick one" class_option :generate, :aliases => '-g', :type => :boolean, :desc => "Don't prompt for action - generate the migration" class_option :migrate, :aliases => '-m', :type => :boolean, :desc => "Don't prompt for action - generate and migrate" def migrate return if migrations_pending? generator = Generators::Hobo::Migration::Migrator.new(lambda{|c,d,k,p| extract_renames!(c,d,k,p)}) up, down = generator.generate if up.blank? say "Database and models match -- nothing to change" return end say "\n---------- Up Migration ----------" say up say "----------------------------------" say "\n---------- Down Migration --------" say down say "----------------------------------" action = options[:generate] && 'g' || options[:migrate] && 'm' || choose("\nWhat now: [g]enerate migration, generate and [m]igrate now or [c]ancel?", /^(g|m|c)$/) if action != 'c' if name.blank? && !options[:default_name] final_migration_name = choose("\nMigration filename: [=#{migration_name}|]:", /^[a-z0-9_ ]*$/, migration_name).strip.gsub(' ', '_') end final_migration_name = migration_name if final_migration_name.blank? up.gsub!("\n", "\n ") up.gsub!(/ +\n/, "\n") down.gsub!("\n", "\n ") down.gsub!(/ +\n/, "\n") @up = up @down = down @migration_class_name = final_migration_name.camelize migration_template 'migration.rb.erb', "db/migrate/#{final_migration_name.underscore}.rb" rake('db:migrate') if action == 'm' end rescue HoboFields::Model::FieldSpec::UnknownSqlTypeError => e say "Invalid field type: #{e}" end private def migrations_pending? pending_migrations = ActiveRecord::Migrator.new(:up, ActiveRecord::Migrator.migrations('db/migrate')).pending_migrations if pending_migrations.any? say "You have #{pending_migrations.size} pending migration#{'s' if pending_migrations.size > 1}:" pending_migrations.each do |pending_migration| say ' %4d %s' % [pending_migration.version, pending_migration.name] end true else false end end def extract_renames!(to_create, to_drop, kind_str, name_prefix="") to_rename = {} unless options[:drop] rename_to_choices = to_create to_drop.dup.each do |t| while true if rename_to_choices.empty? say "\nCONFIRM DROP! #{kind_str} #{name_prefix}#{t}" resp = ask("Enter 'drop #{t}' to confirm or press enter to keep:") if resp.strip == "drop " + t.to_s break elsif resp.strip.empty? to_drop.delete(t) break else next end else say "\nDROP, RENAME or KEEP?: #{kind_str} #{name_prefix}#{t}" say "Rename choices: #{to_create * ', '}" resp = ask "Enter either 'drop #{t}' or one of the rename choices or press enter to keep:" resp.strip! if resp == "drop " + t # Leave things as they are break else resp.gsub!(' ', '_') to_drop.delete(t) if resp.in?(rename_to_choices) to_rename[t] = resp to_create.delete(resp) rename_to_choices.delete(resp) break elsif resp.empty? break else next end end end end end end to_rename end def migration_name name || Generators::Hobo::Migration::Migrator.default_migration_name end end end