July 28, 2013

Replacing rvm.el and rspec-mode

rspec-mode is a cumbersome beast. A large project complete with integration for RVM (using rvm.el), support for multiple ways of running rspec (including through rake), and complete with it’s own customize group.

Coming from Vim, where my rspec test runner consisted of map ;t !bundle exec rspec % this all felt very slow, heavy and inflexible.

While searching for a better way I came across this codemancer post about a simpler “rspec mode”, this approach fits my workflow nicely. It runs a spec file, or a single spec by line number, printing the output to a new window and not a lot else. There’s nothing to configure and everything works by just shelling out to an executable using Compilation mode (which is excellent, by the way).

I set this up as shown in the article, except I already have my own mph-ruby-mode-hook defined so I added the keyboard mappings into that instead.

The only problem I had was that the cd %s failed to read my .ruby-version files. So I had 1.9 apps trying to use Ruby 1.8 sometimes and vice versa.

The approach I took to solve this was to remove some duplication and extract out the command being run to a function:

(defun rspec-format-string (&optional line-p)
    (concat "cd %s && "
        (when (file-directory-p "~/.rvm") "source ~/.rvm/scripts/rvm && rvm use &&")
        "bundle exec rspec %s"
        (when line-p " -l %s")))

Now I could replace those long string commands with a simple (rspec-format-string) and it would source all the crap necessary to make RVM work if I was on a maching with RVM installed.

The only other feature I miss from my .vimrc is the ability to be able to easily jump between code and specs. Here’s my first attempt:

(defun find-spec-from-file ()
    (interactive)
    (save-excursion
        (find-file
            (replace-regexp-in-string
                "\n$" ""
                (shell-command-to-string
                    (concat "find "
                    (get-closest-gemfile-root)
                    " -type f -name "
                    (concat (substring (car (last (split-string (buffer-file-name) "/"))) 0 -3)
                            "_spec.rb")))))))

Admittedly the vim version is more fully featured for now. My elisp function isn’t smart enough to put a new controller spec in spec/controllers for instance, but it will find a spec there if one already exists. It also won’t jump back from spec to code, but Emacs’ buffer switching is easier than Vim’s so I’m not sure that’ll be a problem.

You can find my version of rspec-mode in my emacs repo on Github if you’d like to use it or follow development.