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: 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:

tomw@fellini:~$ cd super-secret-app
~/Projects/apps/super-secret-app
tomw@fellini:~/Projects/apps/super-secret-app$ cd Documents
~/Documents
tomw@fellini:~/Documents$ cd tomafro.net
~/Projects/sites/tomafro.net
tomw@fellini:~/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
}

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