Hashrocket.com / blog

Large badruby

Lets Write Some Bad Ruby

posted on and written by in

Image 100x100 jason cummings

One of my favorite things to do with Ruby is to write a quick throw away script to automate some mechanical changes to my code that can't easily be done with a text editor. Unlike normal code, the code can be downright bad and it's perfectly fine, because once the script runs, we can just delete it.

Sometimes I Don't Want To Type All That

All we care about is the computer doing something for us in a few seconds that would take us much, much longer manually. Here's an example in Rails:

Rails.application.routes.draw do
  namespace :foo do
    resources :bars
    resources :users
    # plus 20 more resources
  end
end

Let's say that for whatever reason, namespacing our resources under 'foo' seemed like the correct decision when we were building our app, but now we've decided not to. We're going to move them out.

Rails.application.routes.draw do
  resources :bars
  resources :users
  # plus 20 more resources
end

We know this is going to break our code in a few ways:

  1. The route helpers. Our bars/index route was foo_bars_path, but now it's simply bars_path. The same concept applies to every other route, and these are bound to be all over our code.

  2. Controller namespacing. Our BarsController looks like, class Foo::BarsController < ApplicationController, so we need to drop the Foo:: namespace off of our controllers.

  3. We need to move everything out of app/**/foo/**, to app/**/**

When dealing with a couple of resources, this isn't really a big deal. But when you have 10, 20, or more resources, doing all that by hand is slow. Sometimes we can accomplish these changes with a text editor, but since we'll have a bunch of path helpers that will all be different (and we'll also be moving files), there will still be a good chunk of manual editing. Luckily, we can write a throwaway script to do it for us!

The Throwaway Script

# cd to root of app
$ touch tmp/move_stuff.rb && cd tmp

We know that our changes are primarily breaking controllers and views. Let's start with views. We want to recursively loop through each view namespaced under foo. For this, we'll use Dir.glob. We'll glob our directories, check to make sure the current file in the iteration isn't a directory, and then find & change our path helpers.

#tmp/move_stuff.rb

Dir.glob '../app/views/foo/**/*' do |file|
  if !Dir.exist? file
    text = File.read file

    text.gsub! /(\w*)foo_(\w+)path/, '\1\2path'
    #puts text.gsub /(\w*)foo_(\w+)path/, '\1\2path'

    File.open(file, 'w') { |f| f.write text }
  end
end

Here we read the file, find anything that matches the format of a path helper that had the foo namespace, gsub it with the first and second captures, and append 'path' to it. Then, we write the file back. Notice, I included the code I used to test the regex when I ran the script, which logged it to the console instead of actually changing anything. I don't want to make any changes until I'm sure I have it right. This applies to any step here.

We also we want to move the file. The current path is the current item in the iteration, file. We can simply gsub out 'foo/' to get the path we want.

file
# '../app/views/foo/bars/index.html.haml'

new_path = file.gsub "foo/", ""
# '../app/views/bars/index.html.haml'

We can get the directory in the new path by matching anything that has word characters followed by a trailing forward slash, like this:

dir_to_move_to = new_path.gsub /.+\//, ""
# '../app/views/bars/'

We can't move to a directory that isn't there, so make it if it doesn't exist:

Dir.mkdir dir_to_move_to unless Dir.exists? dir_to_move_to

And finally, backtick a console mv command to move the file to the new_path:

`mv #{file} #{new_path}`
# `mv ../app/views/foo/bars/index.html.haml ../app/views/bars/index.html.haml`

The final product:

Dir.glob '../app/views/foo/**/*' do |file|
  if !Dir.exist? file
    text = File.read file
    text.gsub! /(\w*)foo_(\w+)path/, '\1\2path'

    File.open(file, 'w')  { |f| f.write text }

    new_path = file.gsub "foo/", ""

    dir_to_move_to = new_path.gsub /.+\//, ""

    Dir.mkdir dir_to_move_to unless Dir.exists? dir_to_move_to

    `mv #{file} #{new_path}`
  end
end

Then go to the /tmp directory and run the file (it needs to be in /tmp in order to use the relative pathnames we wrote, which is probably bad, but I don't care. I'm deleting this in 12 seconds.)

$ ruby move_stuff.rb

And that takes care of the views.

In Conclusion: Yuck

WOW that is some ugly code! The future reader of this code would have to stare at it for a minute or two before getting a handle on what's going on. But here, ugly code is the right tool for the job, because it can be written quickly, and once this script runs, we'll never need to run this again, so we can just delete it. I think this is much easier than doing it by hand (assuming you have a lot of files), plus, the more you write these types of scripts, the quicker you can write them in the future.

For the controllers, you could literally copy and paste (remember, we're deleting this!) the same chunk of code, replace views with controllers, and add a gsub for removing the "Foo::" namespacing.

When you're done, delete this file and never speak of it again!

Posted in Development and tagged with Ruby, rails