Tip: Bundler with --binstubs

In a previous blog, I wrote how I’d aliased commands such as rake, cap and rspec to run either with or without bundle exec, based on the presence of a Gemfile. I gave up on that a while ago. Instead, I’ve started installing all my bundles like this:

bundle install --path .bundle/gems --binstubs .bundle/bin

I often use features like bundle open <gem> to debug and edit failing gems, so I like to keep each application’s gems isolated. The --path .bundle/gems installs them within an application’s .bundle directory. As well as isolating my gems, it has the added benefit that I can blow away the gemset with rm -rf .bundle

The --binstubs .bundle/bin option installs bundle-aware scripts for each command provided by a bundled gem. For example, a bundle including rake will generate a .bundle/bin/rake script. By adding ./.bundle/bin to the front of my environment PATH, the bundled version of rake will run when I’m in the application folder. I never have to type bundle exec!

Obviously typing that long bundle install command each time is tedious, so I’ve aliased it to bi:

alias bi='bundle install --path .bundle/gems --binstubs .bundle/bin'

I’ve been using these options for a few months, and so far I’m very happy with them.

Tip: Automatic bundle exec for rake and other gems

It’s irritating to run gem commands like rake, cap, rspec and others, only to find they needed to be executed via bundle exec. As a simple solution, I use a simple zsh function, combined with aliases for commonly used commands.

Here’s the function (which I’ve named be):

if [[ -a Gemfile ]]; then
  bundle exec $*
else
  command $*
fi

It’s very simple. If there’s a Gemfile in the pwd, it runs commands through bundle exec. Otherwise it just runs them.

I’ve combined this with some aliases for much less pain and less frustration:

alias rake='be rake'
alias cap='be cap'
alias rspec='be rspec'

Tip: Relative paths with File.expand_path

You probably know about the __FILE__ magic constant. It holds the filename of the currently executing ruby source file, relative to the execution directory. So with the following saved as /code/examples/path_example.rb:

puts __FILE__

Running this file from the /code folder will output examples/path_example.rb

This is often used to load files on paths relative to the current file. The way I’ve used it before is like this:

config_path = File.expand_path(File.join(File.dirname(__FILE__), "config.yml"))

This works, but it’s a bit clunky.

What I didn’t realise until reading the rails source code the other day, is that File.expand_path can take a second argument – a starting directory. Also, this argument doesn’t actually have to be a path to a directory, it also accepts a path to a file. With this knowledge we can shorten the above to the following:

config_path = File.expand_path("../config.yml", __FILE__)

Much simpler.

Tip: Zoom keyboard shortcut for OS X

In the Terminal run:

defaults write NSGlobalDomain NSUserKeyEquivalents '{"Zoom" = "@^Z"; "Zoom Window" = "@^Z"; }'

Quit and relaunch your applications, and ⌃⌘Z should zoom and unzoom.

Stolen from macoshints.com, posted here for my own benefit.

Tip: cdpath - Am I the last to know?

This one is just so simple, I can’t believe I didn’t know about it earlier.

First, setup the cdpath or CDPATH variable:

cdpath=(~ ~/Projects/apps ~/Projects/tools ~/Projects/plugins ~/Projects/sites)

Now, changing directory in the shell becomes a whole world easier:

[email protected]:~$ cd super-secret-app
~/Projects/apps/super-secret-app
[email protected]:~/Projects/apps/super-secret-app$ cd Documents
~/Documents
[email protected]:~/Documents$ cd tomafro.net
~/Projects/sites/tomafro.net
[email protected]:~/Projects/sites/tomafro.net $

I’ve already added this to my dotfiles.

Tip: The case for from_param

There’s one small method I add to every new rails project I work on:

module Tomafro::FromParam
  def from_param(param)
    self.first :conditions => { primary_key => param }
  end
end

ActiveRecord::Base.extend(Tomafro::FromParam)

In my controllers, where you might use Model.find(params[:id]) or Model.find_by_id(params[:id), I use Model.from_param(params[:id]) instead.

All three methods have almost the same behaviour, the only difference being the handling of missing records. find throws a RecordNotFound, while find_by_id and from_param return nil. So why use from_param over the others?

The answer comes when you want to change to_param, the method rails uses to turn a record into a parameter. It’s a good principal (though often broken) not to expose database ids in urls. An example might be to use a users nickname rather than their id in user urls, so /users/12452 becomes /users/tomafro. In rails this is easy to achieve, by overriding the to_param method:

class User < ActiveRecord::Base
  def to_param
    self.nickname
  end
end

Rails will automatically use this method when generating routes, so users_path(@user) will return /users/tomafro as we’d like. If I was using find or find_by_id in my controllers, I’d then have to go through each one and change it to find_by_nickname. Luckily though, I’ve used from_param, so whenever I override to_param I just have to remember to provide an equivalent implementation for from_param, and my controllers will work without modification:

class User < ActiveRecord::Base
  def self.from_param(param)
    self.first :conditions => {:nickname => param}
  end
  
  def to_param
    self.nickname
  end
end

I’ve been doing this for years, but it’s hardly a new principle, to provide a from method for every to method. There’s even an old ticket on trac asking for it, but it’s been considered too trivial to add.

I disagree – for me the value comes from having the method from the start, not when you need it. Luckily it’s easy to add to my own projects.

Tip: Open new tab in OS X Terminal

Another simple shell function, this time just for OS X.

Usage is simple: tab <command> opens a new tab in Terminal, and runs the given command in the current working directory. For example tab script/server would open a new tab and run script/server.

tab () {
  osascript 2>/dev/null <<EOF
    tell application "System Events"
      tell process "Terminal" to keystroke "t" using command down
    end
    tell application "Terminal"
      activate
      do script with command "cd $PWD; $*" in window 1
    end tell
  EOF
}

Tip: Create and move to directory

Before my next post on database indexes, here’s a useful little function from the Advanced Command Line peepcode screencast (which I highly recommend).:

# Create and move to a directory in a single command
# Usage: take ~/Projects/tools/awesometer

take() {
  mkdir -p $1
  cd $1
}

Tip: Move to directory and open in TextMate

Since adding this to my zsh configuration, I’m finding I use it all the time:

# Change directory and open TextMate in a single command
# Usage: tm ~/Projects/sites/tomafro.net

tm() {
  cd $1
  mate $1
}

Kernel specific ZSH dotfiles

I work on a number of different machines, OS X based for development and Linux based for hosting. I’ve added various aliases and other commands to my shell, and use a github repository to share this configuration between these machines.

This works well, but while most commands work commonly across Linux and OS X, there are some nasty differences. One example is ls which takes different arguments on both systems; the default ls alias I use on OS X doesn’t work on Linux. So how can we accommodate these differences, without removing all the shared configuration?

The answer is really simple. Create kernel specific configuration files, and use a case statement to load the correct one. The main obstacle was finding a way to distinguish between each kernel. In the end I found the $system_name environment variable, which is set on both OS X and the servers I use.

Here’s the code:

case $system_name in
  Darwin*)
    source ~/.houseshare/zsh/kernel/darwin.zsh
    ;;
  *)
    source ~/.houseshare/zsh/kernel/linux.zsh
    ;;;
esac

As I said, simple.

Pimp my script/console

For a long time I’ve had an .irbrc file and a .railsrc file, setting up some simple helpers methods in my irb and script/console sessions. Today though, I wanted to add some more helpers specific to the project I’m working on. Specifically, I wanted to be able to use my machinist blueprints, to help me play around with some models.

Adding machinist isn’t as simple as just requiring my blueprints though — they require my ActiveRecord models, which aren’t available when .irbrc is loaded. Luckily the solution is simple — just add a couple of lines to the configuration in environment.rb:

Rails::Initializer.run do |config|
  if defined?(IRB)
    config.gem 'faker'
    config.gem 'notahat-machinist', :lib => 'machinist', :source => "http://gems.github.com"
    IRB.conf[:IRB_RC] = Proc.new { require File.join(RAILS_ROOT, "config", "console") }
  end
end

The key part is the line starting IRB.conf[:IRB_RC], which tells irb to run the given when the session starts. Luckily, this happens after rails has finished initializing. I’ve set it to require config/console.rb, to which I can add all sorts of configuration and helpers, knowing it will only get loaded in script/console sessions where I want these shortcuts, not in my general code. Here it is:

require File.expand_path(File.dirname(__FILE__) + "/../spec/blueprints.rb")

def tomafro
  Account.find_by_login("tomafro")
end

def bbi
  Client.find_by_name("Big Bad Industries")
end

Automatching rails paths in cucumber

If you’re using cucumber as part of your testing, you probably have a paths.rb file that looks something like this:

module NavigationHelpers
  def path_to(page_name)
    case page_name
    
    when /the home page/
      root_path
    when /the new client page/
      new_client_path
    when /the clients page/
      clients_path    
    # Add more page name => path mappings here
    else
      raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
      "Now, go and add a mapping in features/support/paths.rb"
    end
  end
end

World(NavigationHelpers)

This let’s us use nice descriptive names in our scenarios, but it starts to become a pain when we add more and more paths. So how can we make it better?

By automatically matching some rails paths. Here’s the code:

module NavigationHelpers
  def path_to(page_name)
    case page_name
    
    when /the home page/
      root_path   
    # Add more page name => path mappings here
    else
      if path = match_rails_path_for(page_name) 
        path
      else 
        raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
        "Now, go and add a mapping in features/support/paths.rb"
      end
    end
  end

  def match_rails_path_for(page_name)
    if page_name.match(/the (.*) page/)
      return send "#{$1.gsub(" ", "_")}_path" rescue nil
    end
  end
end

World(NavigationHelpers)

What it does is pretty simple. Given a page name the clients page (with no other matches defined) it will try and send clients_path. If successful, then it returns the result, otherwise nil.

Not the biggest improvement in the world, but it’s made my cucumber tests just a little bit easier to write.

Adam Sanderson's open_gem

The latest version of rubygems (1.3.2) now has an interface to add commands. Making great use of this feature, Adam Sanderson has written open_gem, a simple but amazingly useful tool.

You use it like this:

$ gem open activerecord

This opens the activerecord gem in your favourite editor (taken from either $GEM_OPEN_EDITOR or $EDITOR environment variables). If there are multiple versions of the gem installed, it will show a menu, letting you choose which version you require.

$ gem open activerecord
Open which gem?
 1. activerecord 2.1.0
 2. activerecord 2.3.2
>

open_gem itself is a gem, and can be installed with:

$ gem install open_gem

To get it working, you need to have $EDITOR set to something sensible:

$ export EDITOR=mate

If you’re running on OS X and use TextMate, you may have already set $EDITOR to mate -w, which let’s you use TextMate as the editor for git commit messages and much more. However, the -w flag doesn’t work with open_gem, so set the $GEM_OPEN_EDITOR variable, and open_gem will use that instead:

$ export GEM_OPEN_EDITOR=mate

You should now be good to go. If you want to see how it works, just use it on itself!

$ gem open open_gem