Skip to content

Instantly share code, notes, and snippets.

@gilesbowkett
Created June 13, 2011 05:38
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gilesbowkett/1022355 to your computer and use it in GitHub Desktop.
Save gilesbowkett/1022355 to your computer and use it in GitHub Desktop.
Rails migrations which automatically put the new filename in your paste buffer
# this monkey-patches code defined in railties-3.1.0.rc1/lib/rails/generators/migration.rb
# (obviously, this might differ very slightly in the latest release candidate)
module Rails
module Generators
module Migration
module ClassMethods
# differs from the "real" version by only one line
def migration_template(source, destination=nil, config={})
destination = File.expand_path(destination || source, self.destination_root)
migration_dir = File.dirname(destination)
@migration_number = self.class.next_migration_number(migration_dir)
@migration_file_name = File.basename(destination).sub(/\.rb$/, '')
@migration_class_name = @migration_file_name.camelize
destination = self.class.migration_exists?(migration_dir, @migration_file_name)
if !(destination && options[:skip]) && behavior == :invoke
if destination && options.force?
remove_file(destination)
elsif destination
raise Error, "Another migration is already named #{@migration_file_name}: #{destination}"
end
destination = File.join(migration_dir, "#{@migration_number}_#{@migration_file_name}.rb")
system("printf #{destination} | pbcopy") # this is that one line
end
template(source, destination, config)
end
end
end
end
# note that if you change the line above, reversing the order of migration_number and
# migration_file_name, you can also get migration filenames which are not, from the perspective
# of experienced Unix users, completely fucking insane in their incomprehensible disregard
# for the incredible usefulness of tab completion (a baffling flaw also shared by Rails's
# config.ru default rackup file name).
# HOWEVER, making that change very likely breaks rake db:migrate, so I'm not going to advocate that
# unless I find time to make sure it works. :-)
@gilesbowkett
Copy link
Author

yeah, it needs some actual tests at this point, but first I just want to be able to install it and get the base use case happening.

I didn't get this part:

You can get around that by using method added. While it generated the file correctly, when I ran the migrations, it did not run.

does that mean you got around it with method added, or you just tried? curious.

@codeodor
Copy link

Actually, it looks like in Rails 3.1, the original file loads, then the init.rb loads and the monkeypatch happens as expected.

However, I was using Rails 3.0.7 which seems to load in the reverse order, so I had to use the self.method_added so I could redefine the method after the original was loaded. I think I have all the working parts for 3.0.7 and 3.1 (no tests, but the base case of swapping the name/version) but its all junk right now and I'm working to clean it up, then fork your project and send a pull request.

If you want to wait for tomorrow or Saturday, I can do that. If you'd rather hack on it now, here's the other monkey patches that I found to be needed:

module Rails
  module Generators
    module Migration
      module ClassMethods
        def migration_exists?(dirname, file_name) #:nodoc:
          migration_lookup_at(dirname).grep(/#{file_name}_\d+.rb$/).first
        end
        def migration_lookup_at(dirname) #:nodoc:
           Dir.glob("#{dirname}/*_[0-9]*.rb")
        end

(then back out of the ends) ....

Your migration_template method needed to swap the name and number.

module ActiveRecord
  class Migrator
    def migrations
      @migrations ||= begin
        files = Dir["#{@migrations_path}/*_[0-9]*.rb"] #swapped order of search pattern

        migrations = files.inject([]) do |klasses, file|
          name, version = file.scan(/([_a-z0-9]*)_([0-9]+).rb/).first #swapped order of search pattern

          raise IllegalMigrationNameError.new(file) unless version
          version = version.to_i

          if klasses.detect { |m| m.version == version }
            raise DuplicateMigrationVersionError.new(version)
          end

          if klasses.detect { |m| m.name == name.camelize }
            raise DuplicateMigrationNameError.new(name.camelize)
          end

          migration = MigrationProxy.new
          migration.name     = name.camelize
          migration.version  = version
          migration.filename = file
          klasses << migration
        end

        migrations = migrations.sort_by { |m| m.version }
        down? ? migrations.reverse : migrations
      end
    end

... and back out of the ends.

I think that's it.

@gilesbowkett
Copy link
Author

I'll take a look tomorrow. It looks pretty straight forward but I'm pretty tired.

@codeodor
Copy link

Forked (the project, not the gist =)) and sent you a pull request. Let me know what you think, how it can be improved, etc.

@gilesbowkett
Copy link
Author

merged. before I forget: I think the method_added comment got copy/pasted to a file which doesn't actually use method_added. I'll take that out later tonight or on the weekend, and then see if I have any other tweaks.

@codeodor
Copy link

Oops. Yeah, I just figured since the other places needed it, it'd be needed there. In fact it's not, so nice catch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment