Yesterday I was looking at some Ruby code that I wanted to change. For context here is the gist of it:
module UserInstrumentation
class StatsdSubscriber < SomeLibrary
...
attach_to "some_topic"
...
end
end
This same pattern played out in a big number of files.
There was a few variations on the topic. The attach_to
statement was located at different places in different files with some at the top and others further down the file. Also some files had multiple classes with attach_to
like:
module UserInstrumentation
class StatsdSubscriber < SomeLibrary
[some number of lines]
attach_to "topic"
...
end
class LogSubscriber < SomeLibrary
[some number of lines]
attach_to 'topic'
...
end
end
What I wanted to do was to replace the attach_to
call of every file with a single initializer file containing direct calls to Rails subscription mechanism like this:
ActiveSupport::Notifications.subscriber(/^\w\.topic$/, UserInstrumentation::StatsdSubscriber)
I could have of course opted for the manual solution and dutifully copied every single module name, class name, and topic into the initializer file. But I wondered if perhaps I could find a faster way doing the same using some of vim's more powerful features. This is what I came up with.
The solution comes in two parts. The first part is setting up a macro for doing this manipulation for a single attach_to
and the second part is running this macro on all instances in the source code.
First up the macro. Before starting the macro I stored the following in register a
:
ActiveSupport::Notifications.subscriber(/^\w\.TOPIC$/, MODULE::CLASS)
The macro recorded in register q
then went like this:
- Move to the first letter of the topic name, store everything until either a single- or a double-quote in register
t
for topic. - Search backwards for a line where the first word is
class
. - Move to the beginning of the first word after
class
and copy it to registerc
for class. - Search backwards for a line with the word
module
. - Move to the beginning of the first word after
module
and copy it to registerm
for module. - Switch to the initializer file.
- Paste register
a
, ie. the line stored above. - Search for
TOPIC
and replace the word with the content of registert
. - Search for
MODULE
and replace the word with the content of registerm
. - Search for
CLASS
and replace the word with the content of registerc
.
As you might imagine it took a few tries to get the macro fully correct.
After recording the macro I wanted to execute it on every instance of attach_to
I could find in the app
directory. To do this I used Ack
to populate the quick list and then execute the macro for every item in the list:
:Ack attach_to app
:cdo execute "normal! @r<cr>"
Did I succeed in my goal of performing the edit faster than if I had opted for doing the edits manually? I don't know. It took a while to get the macro correct. My guess would be that I used roughly the same amount of time, perhaps a bit more. But I consider this an investment. The next time I see a change I need to do to a large number of files I know the mechanics and I will be able to do it faster -- probably a lot faster than a manual edit.