giant robots smashing into other giant robots

Written by thoughtbot

lolconomy

Integrating vim into your life

More tips from thoughtbot about using vim, but this time with an emphasis on fitting it into your life.

Copy and paste

To copy and paste from your PRIMARY (on OS X, your clipboard) you use the register. For example, to paste from something you’ve copied elsewhere use "p and to copy the current line into your system-wide buffer use "*yy .

For those of us with both a PRIMARY and a CLIPBOARD, the * register is the PRIMARY and the + register is the CLIPBOARD.

Reducing distractions

You can fullscreen MacVim to block out other distractions with :set fuoptions=maxvert,maxhorz and then :set fullscreen. You can get back with :set nofullscreen

File management

Converts from IDEs like Visual Studio like Nick might miss a file explorer for your project. Luckily, NERDTree comes to the rescue. Packed with plenty of good shortcuts, this helpful plugin saves Nick a lot of time when a simple :e or using the :R macros in rails.vim just won’t cut it.

Search results from the command line

While Jason blogged about integrating Ack into vim before, here’s a handy shell script to open a new vim with search results from the command line.

editor=${VISUAL:-vim}
if [ "$#" = "1" ]; then
  $editor -c "/$1" $(grep -l $1 **/*)
elif [ "$#" = "2" ]; then
  $editor -c "/$1" $(grep -l $1 $2) 
else
  $editor $(grep -l $@) 
fi

Save that as vg then, to open all files that mention current_user from the command line run: vg current_user

The full blame

You see that weird piece of code? Who checked that in?! Why is it even there?!

Add these to your .vimrc to get the quick blame for any highlighted lines (\b for svn, \g for git, and \h for Mercurial):

vmap <Leader>b :<C-U>!svn blame <C-R>=expand("%:p") <CR> \| sed -n <C-R>=line("'<") <CR>,<C-R>=line("'>") <CR>p <CR> 
vmap <Leader>g :<C-U>!git blame <C-R>=expand("%:p") <CR> \| sed -n <C-R>=line("'<") <CR>,<C-R>=line("'>") <CR>p <CR> 
vmap <Leader>h :<C-U>!hg blame -fu <C-R>=expand("%:p") <CR> \| sed -n <C-R>=line("'<") <CR>,<C-R>=line("'>") <CR>p <CR>

ctags

Exuberant ctags is a program that scans source files for keywords and supports many languages, including Ruby. Jumping to a defined tag is much faster and easier than searching for it in your project using Ack or Grep, and Vim integrates with ctags nicely.

You can create a tags file using the ctags command (run ctags --help for options), but if you’re using the excellent rails.vim plugin by Tim Pope, you can run the :Rtags command from Vim. Running this command only takes a moment, and will generate a tags file containing all the keywords and locations in your project. Note that you’ll have to regenerate your tags file using the same command for it to pick up new keywords.

Once you have a tags file, you can jump to a tag by using the :tag command:

:tag ensure_user_is_admin

Or by pressing Ctrl+] when the cursor is over a keyword. If there is more than one match for a tag, you can use :tn (next tag), :tp (previous tag), and :ts (select from a list) to navigate through matches. Again, this is much faster than searching.

Another benefit of using ctags is that you can use it for tab completion. I find that tab completion becomes unusably slow in a large project if you’re finding keywords from open buffers, but you can tell Vim to only use the current file and ctags when finding keywords:

:set complete=.,t

Completion results with this setting are instantaneous.

[Contributions by Jon Yurek, Nick Quaranto, and Joe Ferris.]

lolconomy

Testing Rake’s Integration

So now you’re integration testing, because that’s what cool kids are doing these days. This tests the joints of your app, making sure that the model code is being called from the controller code which is being invoked by the user.

A good integration test, as we all know, is from the user’s perspective: “I click this”, “I fill in that”, etc.

But what of rake tasks? Those are integration points. If they go wrong you’re depending on cron telling you, or maybe it’s a task you run once a year by hand. Either way, you want to test it.

We already move all of the rake task into the model. This simplifies unit testing; rake tasks are now just one method call, and that method is isolation tested.

Here’s an integration test for a rake task that sends an email with all new users:

require 'test_helper'

class DailyEmailReportTest < ActionController::IntegrationTest
  # In order to keep updated, I should see an email
  # of all new users' profile in the past day

  should "send an email from the rake test for the specific task" do
    old_user = Factory(:user,
                       :created_at => 2.days.ago,
                       :description => "old description")
    new_user = Factory(:user,
                       :created_at => 1.hour.ago,
                       :description => "new description")
    ActionMailer::Base.deliveries.clear

    i_call_rake_task "notifications:daily:users"

    i_see_in_email "new description"
    i_do_not_see_in_email "old description"
  end
end

Two test helpers are defined to help out: #i_see_in_email and #i_do_not_see_in_email. In addition to those (they just look over ActionMailer::Base.deliveries), we have #i_call_rake_task. That’s where the magic happens.

The first step is to override Rake::Task#invoke_prerequisites to avoid reloading the environment:

require 'rake'

# Do not re-load the environment task
class Rake::Task
  def invoke_prerequisites(task_args, invocation_chain)
    @prerequisites.reject{|n| n == "environment"}.each do |n|
      prereq = application[n, @scope]
      prereq_args = task_args.new_scope(prereq.arg_names)
      prereq.invoke_with_call_chain(prereq_args, invocation_chain)
    end
  end
end

Then we define the helper:

class Test::Unit::TestCase
  def i_call_rake_task(task_name)
    # Make sure you're in the RAILS_ROOT
    oldpwd = Dir.pwd
    Dir.chdir(RAILS_ROOT)

    # Get an instance of rake
    rake_app = Rake.application
    rake_app.options.silent = true

    # Back to where you were
    Dir.chdir(oldpwd)

    rake_app.init
    rake_app.load_rakefile

    task = rake_app.tasks.detect {|t| t.name == task_name}
    assert_not_nil task, "No rake task defined: #{task_name}"
    task.reenable
    task.invoke
  end
end

With this in place, you can be more sure that rake tasks are integrated with your system. Wee!

Jumping into a pile of leaves