-
-
Save gilesbowkett/1022355 to your computer and use it in GitHub Desktop.
# 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. :-) |
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
I'm glad someone said it. But I don't know if it qualifies as insane only to experienced Unix users. I think it goes for inexperienced folks as well.
I'd actually like to figure out the migration-running side of this and submit the whole thing as a patch / release it as a gem. I don't think this is any kind of hostility to tab-completion, just cluelessness because so many Rails devs use TextMate. obviously useful enough for gem status and maybe for patch status as well.
Am I mistaken or didn't Rails 1.x series have the migration name at the start of the file?
I use TextMate, but I always like to open new migrations up via the command line because TM's file browser does not refresh quick enough for me, plus I'm already there from having typed rails g migration ...
Or maybe it was ordered with a short number then the name...
heh, sry for the TextMate bigotry. I don't remember, but it's still worth doing -ish
It's kind of clunky, especially since you need a unique string to make it work well, but you could use something like:
ls db/migrate/*project<esc><g>
to autocomplete. But if 'project' is not uniquely found in one file name, you end up with the same problems of the normal tab completion.
No worries about "TextMate bigotry." I didn't notice it and I certainly wasn't offended. =)
I would like to see a patch to make it more usable for humans. If I care about which order the migration should come in, I can sort by create date.
So I agree it's worth "doing -ish"
well, good news and bad news. I have code which works but I haven't got it packaged properly.
https://github.com/gilesbowkett/Rosewood-Migrations
I did it as a gem, it failed, redid it as a plugin, still failing. might have to do some load path trickery, might just be something obvious I haven't figured out yet. by all means take a look!
figured it out, maybe. need a generator which puts the code in lib/tasks and config/initializers
If the whole rails stack gets loaded, shouldn't the plugins too? I'll play around with it later today!
Oh, I suppose the problem is not in having it run the migration, but in generating them.
For generating migrations:
Verified that moving it to the initializers dir causes it to get loaded. Also, adding an init.rb to the root directory w/ require 'rosewood_migrations'
causes the plugin to get loaded (if you install it as a plugin).
Unfortunately, it appears to get loaded before Rails::Generators::Migration does, because there doesn't seem to be any affect to putting it in there.
You can get around that by using method added. While it generated the file correctly, when I ran the migrations, it did not run.
def self.method_added(method_name)
@@monkey_patching ||= false
if method_name.to_s == "migration_template" && !@@monkey_patching
class_eval do
@@monkey_patching = true
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
# These next two lines of code represent the sum total of all deviations from the
# the original code base contained within this particular file.
destination = File.join(migration_dir, "#{@migration_file_name}_#{@migration_number}.rb")
system("printf #{destination} | pbcopy")
end
template(source, destination, config)
end
end
end
(Also note I changed the order of the destination
file name: it had number first, then name.
I think more than the last 2 lines you've commented as the only change need to be changed. Because if name only gets switched to the front at that point, then above where it checks for existing migration of the same name, it won't find the existing ones.
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.
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.
I'll take a look tomorrow. It looks pretty straight forward but I'm pretty tired.
Forked (the project, not the gist =)) and sent you a pull request. Let me know what you think, how it can be improved, etc.
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.
Oops. Yeah, I just figured since the other places needed it, it'd be needed there. In fact it's not, so nice catch.
http://twitter.com/#!/Nemo157/status/80163062560198656