3 basic refactoring tools for your command line
Nowadays, I don't use any fancy editor or IDE, but for many years I worked with Eclipse, and I loved all the Java code refactoring tools it offered, even though most people only use pretty simple refactorings. So I used to believe that I needed things like RubyMine if I wanted to have the best refactoring tools, now I know I don't. I currently spend most of my time editing Ruby code with VIM, doing a lot of refactoring, and I have had a very satisfying experience.
The operations I usually do fall into 3 main categories/tools, I'm going to include a short description of each category followed by an example of its use.
First, let's install the utilities:
-> brew install mmv the_silver_searcher ack
And now, a quick summary of the commands I use the most:
-> ack 'pattern' # lists the places in the files where pattern appears -> ag 'pattern' # same as above but using Silver Searcher -> ag -l 'pattern' # lists names of the files where pattern appears -> ag -l 'pattern' | xargs sed -i 'backup' 's/pattern/other_pattern/' # replaces pattern by other_pattern on all of the files where pattern appears, creates .backup files -> ag -l 'A\s+Pattern' | xargs sed -i '' -E 's/A(\s+)Pattern/Some\1Pattern/' # same as above but using back references -> mmv "spec/file_*_spec.rb" "spec/#1_file_spec.rb" # Mass rename
Now let's give a more in-depth explanation.
1. Searching file contents with ack/ag
Some people use grep, but I prefer ack because of three reasons: it is faster, easier and cleaner. Google that and confirm what I say. Nowadays, I'm using ag, the Silver Searcher more often, which claims to be 3-5x faster than ack. The ag and ack command line have a very similar interface, so I use them indistinctly.
a) ag ActiveRecord
This is to search the Rails codebase for the ActiveRecord constant usage:
-> ag ActiveRecord activerecord/test/models/movie.rb 1:class Movie < ActiveRecord::Base activerecord/test/models/order.rb 1:class Order < ActiveRecord::Base activerecord/test/models/organization.rb 1:class Organization < ActiveRecord::Base
Ever wondered how many times is that constant used? Here’s how to do it:
-> ag ActiveRecord|wc -l 3677
b) ag 'def save'
If you want to search the Rails codebase for a #save method:
-> ag 'def save' actionpack/test/lib/controller/fake_models.rb 92: def save; @id = 1; @post_id = 1 end 113: def save; @id = 1; @post_id = 1 end 133: def save; @id = 1; @comment_id = 1 end 147: def save; @id = 1 end 163: def save; @id = 1; @tag_id = 1 end actionview/test/lib/controller/fake_models.rb 65: def save; @id = 1; @post_id = 1 end 86: def save; @id = 1; @post_id = 1 end 106: def save; @id = 1; @comment_id = 1 end 120: def save; @id = 1 end 135: def save; @id = 1; @tag_id = 1 end
c) ag -l 'def save'
To list the files in the Rails codebase containing a #save method.
-> ag -l 'def save' actionpack/test/lib/controller/fake_models.rb actionview/test/lib/controller/fake_models.rb activemodel/lib/active_model/dirty.rb activemodel/test/cases/dirty_test.rb activerecord/lib/active_record/attribute_methods/dirty.rb activerecord/lib/active_record/attribute_methods.rb activerecord/lib/active_record/autosave_association.rb
2. Replacing files contents with sed
I don't actually know that much about sed, but I do understand the basics, and I’ve found that that is more than enough to make pretty strong refactorings.
Let's work a bit more over the previous example cases.
a) ag -l ActiveRecord | xargs sed -i '' 's/ActiveRecord/ActiveDoom/'
In the Rails codebase rename ActiveRecord modules to ActiveDoom.
I always refactor control versioned code because it helps me avoid using the -i "extension" sed parameter (more on this in the next paragraphs).
-> ag -l ActiveRecord | xargs sed -i '' 's/ActiveRecord/ActiveDoom/'
This a two-step process:
First, list all file names containing the ActiveRecord string:
-> ag -l ActiveRecord
Second, use each file name to construct an argument list with xargs and invoke the sed utility.
sed is using two arguments, the first one is -i "", which basically creates backup files with the extension in quotations, none in this case, hence the empty "". In the second argument we pass the command 's/ActiveRecord/ActiveDoom/'.
The sed arguments’ syntax may vary depending on the version you are using, so make sure you check the man sed for the version you are using.
3. Moving files with mmv
Now, let's work with active_merchant. More specifically; its gateways. Here I see a class name ActiveMerchant::Billing::ModernPaymentsGateway, so I would expect its file name to be activemerchant/billing/modern\payments_gateway.rb.
What I want is to insert the _gateway word in all gateway classes.
-> mmv "lib/active_merchant/billing/gateways/*.rb" "lib/active_merchant/billing/gateways/#1_gateway.rb"
Let's see the first five files:
-> tree lib/active_merchant/billing/gateways |head -n 5 lib/active_merchant/billing/gateways ├── authorize_net_cim_gateway.rb ├── authorize_net_gateway.rb ├── balanced_gateway.rb ├── banwire_gateway.rb ├── barclays_epdq_extra_plus_gateway.rb ├── barclays_epdq_gateway.rb
Look at the files’ new prefix, it worked.
Finally, another good reason to use these tools is the fact that they do not depend on an IDE or language.