Creating an application form style layout using Prawn

I used the excellent Prawn gem recently to create a PDF. I needed an application form style like layout for the PDF like this:

Simple form layout

Simple form layout

Using Prawn this is straight forward. We just create two bounding boxes one for the left side and one for the right side of the page. Then we render the labels into the left bounding box and the form boxes into the right bounding box.

Filling in the fields

filled-in-form

Filled in form

In some case I need to fill in the fields as well so I generalised the code into a method that takes a hash:

Validating UK postcodes with ActiveRecord validations

Validating UK postcodes with ActiveRecord is pretty straightforward. I grabbed a regular expression from this blog post and made it slightly more forgiving to accept postcodes without spaces (so that it accepts both SW1 1AB and SW11AB).

Here is a version with the regular expression inline:

And here is the custom validator version:

To use the custom validator bung the file in app/validators/ and use like so:

Stripping strings in a Mongoid document

When taking in user input on a web form you usually want to strip/trim the text the user has entered. I wrote a little Mongoid extension class to do this. The fields need to explicitly declared for this to work. To use it just add include Mongoid::StringStripper to your document class.

module Mongoid
  module StringStripper
    extend ActiveSupport::Concern

      included do
        set_callback :save, :before, :strip_strings
      end

      def strip_strings
        field_names = fields.map {|x| x[0] }.reject{ |x| x[0] == "_"  }
        field_names.each do |field_name|
          value = self.send(field_name)
          if value and value.respond_to?(:strip)
            self.send(field_name + "=", value.strip)
          end
        end
      end
  end
end

Here is usage example/test:

require 'test_helper'

class TestStippee
  include Mongoid::Document
  include Mongoid::StringStripper

  field :a, type: String
  field :b, type: String
  field :c, type: Integer

end

class StringStripperTest < ActiveSupport::TestCase
  test "When mongoid doc is saved the strings are stripped" do
    o = TestStippee.new(:a => "stripped ", :b => "unchanged", :c => 1)
    o.save
    assert_equal o.a, "stripped"
    assert_equal o.b, "unchanged"
    assert_equal o.c, 1
  end
end

My epic year of learning at Forward

Last year was a big year for me learning wise. uSwitch has always had a good learning environment but since we became part of the Forward Internet Group the pace has stepped up a level. At the end of 2010 I moved back to uSwitch from uSwitch for Business. This coincided with the company transitioning from the .Net stack to the Unix stack – a transition I am happy to have made. I now find myself more enthusiastic about programming and technology. Best of all I got to work with some really smart people!

Ruby

I had previously used Ruby for testing, admin sites and scripting, but in 2011 Ruby became my primary development language. Working on it day-to-day I was able to move from noob to vaguely competent pretty quickly. It is a great language whose flexibility and expressiveness really shines through. Many thanks to Siddharth Dawara, Marcin Ciszak, Andrew Nesbitt, Paul Ingles and Michael Patricios for all their patience with me whilst I struggled to get my head around the new stack. Also, a big thanks up to Fred George whose two Object Oriented Ruby boot camps helped a lot. Outside work Chad Fowler’s session at the Scottish Ruby Conference was also a big gap filler, as were the Ruby koans, and the Elegant Ruby book.

Ruby on Rails

Most of the sites I built last year were with Rails. It is a big framework with a lot to learn, but the conventions it teaches you are smart (and transferable). The new asset pipeline features in 3.1 pushed me into learning the basic of CoffeeScript and finding out more about website speed optimisations. Michael Hartl’s Ruby on Rails Tutorial and the Rails Way were my main learning resources.

Unix and Ops

The steepest learning curve of the year was becoming competent with the tools for our Unix infrastructure. At Forward, we have a couple of Dev Ops guru to help us with the hard stuff, but developers do a lot of the basic infrastructure tasks (such as creating servers and diagnosing deployment issues). I had to get my head around tons of stuff: living in the terminal, shell scripting, basic server admin, nginx/apache configuration, monit/upstart, puppet and our ec2/cloud infrastructure. Again massive thanks to Sid, for showing me how to setup my profile like a pro, Noah Cantor for helping me solve the stuff that was well beyond me and Tom Hall for his Upstart tutorial. I also found the two Unix peepcode screencast to be an invaluable introduction.

Clojure and functional programming

At the end of 2010 some of the developers at Forward ran a study group on the seminal Structure and Interpretation of Computer Programs (SICP). It was probably the hardest technical book I have read since college but also the most rewarding. We wrote the exercise in the book in Clojure. I had played around with higher order functions like LINQ before and learnt a bit of F#, but this was the first time I had learned a functional language in any depth. I even got to write a little bit of production Clojure code thanks to Mike Jones and Jon Neale who had to put up with me fumbling around in Emacs. The peepcode screencast on Emacs was a life saver here and I found Programming Clojure was the best beginners book for me.

Public speaking

I gave my first talk at a conference, speaking about how to do Ruby on a Windows/.Net project at the Scottish Ruby Conference! I also gave two talks at Forward’s regular monthly event First Tuesday. Some good presentation tips came from Presentation Zen, although my presentation style is still very rough around the edges.

German

The perks are pretty ace at Forward and last year free language course were on offer. I jumped at the chance to learn German. In school, I sucked at languages and hated being forced to do compulsory Irish classes. Happily German has being going really well. Maybe this is because I visit Germany so often and have become acclimatised to the language. The language courses are much more conversational than the rote learning I did at school. I find German to be very close to English and am fascinated by the linguistic similarities. Also, the straight-forward structure of German seems to have clicked with me.

There are some fantastic resources out there for learning languages. I listened to the Michel Thomas German audio course and Deutsche Welle Audio Tutor daily when walking to work. Repeating German to myself probably got me strange looks, but luckily in London you don’t know your neighbours. Recently, I have become totally addicted to Babbel’s free iPhone vocabulary trainer and Online course.

Statistics and R

Over the last few years my lack of statistics knowledge has stopped me in my tracks when approaching some data analysis problems. Last year I decided to do something about it and learn statistics and the R programming language. Here again Forward was the right place to be working. Alex Farquhar ran an intro to R session, and Abigail (the Forward Statistician!) ran a nice stats overview class. In August, I went to the useR conference at the University of Warwick. To be honest several of the talks went over my head but it was great to go to a conference outside my comfort zone.

Since then statistical techniques and thinking have come in extremely handy on recent projects. Statistics without Tears: An Introduction for Non-Mathematicians was my gentle introduction and I have been steadily working my way through the superb Khan academy statistics course. The RStudio IDE got me up and running with R and I got the basics from the Quick-R website. R in Action was the only book I could find that was wasn’t overload with statistics formulas.

Web development

Responsive web design was a big revelation for me in 2011. With so much internet usage now via mobile devices, having a site optimised for mobile is no longer a nice to have feature. I first heard about Responsive web design at a First Tuesday talk Luke Williams gave. At uSwitch, our front end guru Emma Sax did an amazing responsive styleguide which has made front end development work much easier. I recommend checking out Skelton which does a similar job.

I have steadily got into the habit of incorporating HTML 5 style markup into my work. Using the new input types like telephone lets you easily improve the user experience. I also hacked together a Wordcloud to learn about the canvas tag. Dive into HTML 5 is a nice introduction to the new features that are available.

Google Analytics

I had a basic understanding of Google Analytics, but last year I invested some serious time learning it properly. Teams at Forward take a lot of ownership over the work they do and the metrics in software like Google Analytics are critical to get insight into how customers are interacting with your websites. For nearly every feature I was involved in building, I made sure they was at some way to measure it usage (often by using custom events). I recommend watching the Google Analytics IQ Lessons which will give you a thorough overview.

The Environment

Most my work last year was centred around building new energy efficiency websites at uSwitch. I have always been interested in environmental issues so this was the perfect area for me to be working in. At the start of the year we built an energy-saving products shop on top of the great work Andrew Nesbitt and Michael Patricios did at Just Shops. I also ate my own dogfood and installed an energy monitor and a standby saver at home.

Later on in the year, Hemal Kuntawala and I built a Solar power website which lets the public find solar installers in their area. Hemal brought a lot of expertise from his side project Mixeeba along with an inspiring “just do it” attitude.

Side projects

I started a couple of little side projects mainly to learn Ruby, Rails and MongoDB. The main one was to aggregate content from Twitter links, but since I started using the amazing Flipboard application, I quickly gave up on the idea. As a learning technique I have found building my own stuff to be the most effective way to pick up new technologies. Only HTML to Hemal eventually saw the light of day!

What a year…

Coming up soon what I am planning to learn in 2012!

How to mostly do Ruby on a Windows/.net project lightning talk

The lightning talk How to mostly do Ruby on a Windows/.net project I gave at the Scottish Ruby Conference in April is now online. Some topics I discuss include:

  • why we didn’t use IronRuby
  • the pain of using ODBC and SQL server with Ruby
  • Custom hyperlinks protocols
  • Micro webservices

My talk starts at 22 minutes in and is lightning quick at about 10 minutes long. As an added bonus you get my colleague Jon Neale talking about Arduino robots and Twitter from 39 minutes in.

Life without IntelliSense in Ruby. Part 2: the IRB

In my previous post I talked about using some of TextMate’s features to make up for the lack of Visual Studio style IntelliSense. IntelliSense makes it easy to discover the APIs of the objects that we are working with. A benefit of using an interpreted language such as Ruby is the availability of a REPL (Read-eval-print-loop). Ruby’s REPL is called the Interactive Ruby Shell (IRB). We can use the IRB to discover the APIs of the objects and Gems we are working with. The IRB also gives us another benefit over IntelliSense – the ability to quickly experiment with the objects in our programs.

To start the IRB from the terminal type irb (unsurprisingly!).

admin ~ $ irb

This produces a prompt like this, where you can type and evaluate Ruby commands:

ruby-1.8.7-p302 :001 >

The Methods Method Method for Discovering Methods

We can use Ruby’s reflection functionality to find the methods available on any object we are working with by using the methods method. For example to find the methods available on a string:

"".methods
=> ["upcase!", "zip", "find_index", "between?", "unpack", "enum_slice", "to_f", "minmax", "lines", "sub", "methods", "send", "replace", "empty?", "group_by", "squeeze", "crypt", "gsub!", "taint", "instance_variable_defined?", "match", "downcase!", "take", "find_all", "min_by", "bytes", "each_cons", "entries", "gsub", "singleton_methods", "instance_eval", "to_str", "first", "chop!", "intern", "nil?", "succ", "capitalize!", "take_while", "select", "max_by", "chars", "enum_cons", "tr!", "protected_methods", "instance_exec", "display", "sort", "chop", "tainted?", "dump", "method", "include?", "untaint", "instance_of?", "chomp!", "swapcase!", "drop", "equal?", "reject", "hex", "minmax_by", "sum", "hash", "private_methods", "all?", "tr_s!", "sort_by", "chomp", "upcase", "start_with?", "succ!", "kind_of?", "strip!", "freeze", "drop_while", "eql?", "next", "collect", "oct", "id", "slice", "casecmp", "grep", "strip", "any?", "delete!", "public_methods", "end_with?", "downcase", "%", "object_id", "is_a?", "scan", "lstrip!", "cycle", "map", "member?", "tap", "type", "*", "split", "insert", "each_with_index", "+", "count", "lstrip", "one?", "squeeze!", "instance_variables", "__id__", "frozen?", "capitalize", "next!", "each_line", "to_enum", "rstrip!", "to_a", "ljust", "respond_to?", "upto", "each", "inject", "tr", "slice!", "class", "reverse", "length", "enum_with_index", "rpartition", "rstrip", "<=>", "none?", "instance_variable_get", "find", "==", "swapcase", "__send__", "===", "min", "each_byte", "enum_for", "extend", "to_s", "rjust", "index", ">=", "size", "reduce", "tr_s", "<=", "clone", "reverse_each", "to_sym", "bytesize", "=~", "instance_variable_set", "<", "detect", "max", "each_char", "each_slice", ">", "to_i", "center", "inspect", "[]", "reverse!", "rindex", "partition", "delete", "[]=", "concat", "sub!", "dup", "<<"] 

I usually append a sort call to make the output alphabetic and easier to scan:

"".methods.sort
 => ["%", "*", "+", "<", "<<", "<=", "<=>", "==", "===", "=~", ">", ">=", "[]", "[]=", "__id__", "__send__", "all?", "any?", "between?", "bytes", "bytesize", "capitalize", "capitalize!", "casecmp", "center", "chars", "chomp", "chomp!", "chop", "chop!", "class", "clone", "collect", "concat", "count", "crypt", "cycle", "delete", "delete!", "detect", "display", "downcase", "downcase!", "drop", "drop_while", "dump", "dup", "each", "each_byte", "each_char", "each_cons", "each_line", "each_slice", "each_with_index", "empty?", "end_with?", "entries", "enum_cons", "enum_for", "enum_slice", "enum_with_index", "eql?", "equal?", "extend", "find", "find_all", "find_index", "first", "freeze", "frozen?", "grep", "group_by", "gsub", "gsub!", "hash", "hex", "id", "include?", "index", "inject", "insert", "inspect", "instance_eval", "instance_exec", "instance_of?", "instance_variable_defined?", "instance_variable_get", "instance_variable_set", "instance_variables", "intern", "is_a?", "kind_of?", "length", "lines", "ljust", "lstrip", "lstrip!", "map", "match", "max", "max_by", "member?", "method", "methods", "min", "min_by", "minmax", "minmax_by", "next", "next!", "nil?", "none?", "object_id", "oct", "one?", "partition", "private_methods", "protected_methods", "public_methods", "reduce", "reject", "replace", "respond_to?", "reverse", "reverse!", "reverse_each", "rindex", "rjust", "rpartition", "rstrip", "rstrip!", "scan", "select", "send", "singleton_methods", "size", "slice", "slice!", "sort", "sort_by", "split", "squeeze", "squeeze!", "start_with?", "strip", "strip!", "sub", "sub!", "succ", "succ!", "sum", "swapcase", "swapcase!", "taint", "tainted?", "take", "take_while", "tap", "to_a", "to_enum", "to_f", "to_i", "to_s", "to_str", "to_sym", "tr", "tr!", "tr_s", "tr_s!", "type", "unpack", "untaint", "upcase", "upcase!", "upto", "zip"] 

Another handy trick is to use the Array’s grep method to see just the methods you are interested in.

"".methods.grep /each/
 => ["each_cons", "each_with_index", "each_line", "each", "each_byte", "reverse_each", "each_char", "each_slice"] 

Playing with Gems

When experimenting with a new gem it is straightforward to just load them into the IRB using require. In most Ruby projects there is a convention to have an init.rb or environment.rb file that loads all the libraries used by the project and sets up database connections etc. In the IRB you can require this file and then play around with the objects in your project. Rails has this feature built-in: if you type rails console (or script/console in Rails 2) an IRB opens with all the project’s classes loaded and a database connection established. You can then use the rails console to create or retrieve active record objects and inspect their state. For example:

User.first
 => #<User id: 1, name: "Jason Neylon", email: "jason@gmail.com", created_at: "2011-01-07 19:40:34", updated_at: "2011-01-07 19:40:34"> 

The rails console is also great for ad-hoc administration or investigation of production issues.

Bye Bye IntelliSense Hello Fast Feedback

I would be lying if I said I didn’t sometimes miss Visual Studio’s IntelliSense features. But I love the ability that the IRB gives you to quickly experiment with objects. The fast feedback really helps to make development more productive. The Visual Studio/C# compile/test/debug cycle seems slow and painful in comparison.

Instagr.am Picture Previews with Ruby

In my previous post I discusses how to get image previews from TwitPic, yfrog and plixi. Getting preview pictures from instagr.am is slightly harder. It is worth the extra effort however as instagr.am is popular at the moment and the pictures tend to be better than the other services as people take more care with what they post.

Getting the picture preview url

Currently there is no official/supported API for this so I referred to mislav’s unofficial API documentation and ruby library. In order to get the picture preview URL you need an instagr.am page link:
http://instagr.am/p/BT7lU/
. You then make a GET request to the instagr.am API using the page link as a parameter and specifying an additional maxwidth parameter: http://instagr.am/api/v1/oembed/?url=http://instagr.am/p/BT7lU/&maxwidth=150.

This returns the following JSON:

{
	"provider_url": "http://instagr.am/", 
	"title": "Cupcake!", 
	"url": "http://distillery.s3.amazonaws.com/media/2011/01/31/ad710c1abd964cbd8f89e09cc7c76eab_5.jpg",
 	"author_name": "andrewnez", 
	"height": 150, 
	"width": 150, 
	"version": "1.0", 
	"author_url": "http://instagr.am/", 
	"provider_name": "Instagram", 
	"type": "photo"
}

The url field of this JSON document contains a direct link to the image preview:
instagr.am picture preview

You can get a link to a larger image by passing a larger value to the maxwidth parameter such as http://instagr.am/api/v1/oembed/?url=http://instagr.am/p/BT7lU/&maxwidth=650.

Here is the ruby code to do this:

def instagram_image_url(url)
    json = get_url(URI.parse("http://instagr.am/api/v1/oembed/?url=#{url}&maxwidth=200"))
    doc = JSON.parse(json)
    doc["url"]
end

# Plundered from https://github.com/mislav/instagram/blob/master/lib/instagram.rb
def get_url(url)
	response = Net::HTTP.start(url.host, url.port) { |http|
	  http.get url.request_uri, 'User-agent' => 'http://www.whoeveryourare.com'
	}

	if Net::HTTPSuccess === response
	  response.body
	else
	  response.error!
	end
end

Performance Issues when Querying the API

Querying the instagr.am API as you are processing a twitter stream can mean a lot of (probably slow) requests to external servers. If you want your webpage to respond quickly this isn’t ideal. A simple workaround is to use a redirection URL on your site. As you process the twitter stream you extract a unique ID for each picture. The redirection URL then takes that ID, makes the JSON request and redirects the browser to the appropriate picture.

This redirect code is pretty straight-forward, here is a sinatra based example

get "/" do
	instagram_preview_html("http://instagr.am/p/BT7lU/")
end

get "/instagram/:id" do
  redirect instagram_image_url(params[:id]), 301
end

def instagram_preview_html(url)
	id = url.split("/").last
	"<a href=\"#{url}\"><img src=\"/instagram/#{id}\" title=\"#{text}\"/></a>"
end

def instagram_image_url(id)
	url = "http://instagr.am/p/#{id}"
	json = get_url(URI.parse("http://instagr.am/api/v1/oembed/?url=#{url}&maxwidth=200"))
	doc = JSON.parse(json)
	doc["url"]
end

The techniques for other services with Restful APIs, such as Flickr are similar. It would be great if Instagr.am followed the lead of other picture services and made their pictures directly addressable and hackable based on their URLs without having to use the JSON API. Hopefully as their web presence grow they will do so.

Twitter Picture Previews with Ruby

As part of a pet project (coming soon!) I have been investigating extracting previews (thumbnails) of pictures from services such as TwitPic, yfrog and plixi in Twitter feeds. Last week I added this feature to the Forward technology website. It is a neat feature as it adds some visual spice to the page. It also gives some insight into what we get up to at work (mostly being geeky and drinking too much).

The hard(ish) bit

What makes this process a little bit awkward is the sheer number of different twitter picture services that are out there. I decided to handle the most popular amongst the people I follow: TwitPic, yfrog and plixi. None of these services share a common interface but after a bit of Googling (this article was especially helpful) and digging around the API documentation (TwitPic API page, yfrog API page and plixi API page) I came up with the following translation:

Service Picture page URL Picture preview URL Example page URL Picture preview
TwitPic http://twitpic.com/{id} http://twitpic.com/show/thumb/{id}.jpg http://twitpic.com/ibhev
http://twitpic.com/show/thumb/ibhev.jpg
yfrog http://yfrog.com/{id} http://yfrog.com/{id}:small http://yfrog.com/bggccwj
http://yfrog.com/bggccwj:small
Plixi http://plixi.com/p/24330722 http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=The picture page url http://plixi.com/p/24330722
http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=http://plixi.com/p/24330722

Generally the link to the picture page will contain a unique ID that identifies the picture. This ID is easy to extract and use to build the URL to the image preview.

The easy bit

After figuring out the how the URL the code is pretty straightforward. I use the Twitter gem to search for tweets with links from the user on my timeline and then build a set of image previews.

require 'twitter'
require "yajl"

class TwitterPictures

  class TwitpicAdapter
    def self.match?(url)
      !(url =~ /twitpic\.com/).nil?
    end

    def self.html(url, text)
      uri = URI.parse(url)
      id = uri.path
      "<a href="\&quot;#{url}\&quot;"><img title="\&quot;#{text}\&quot;/" src="\&quot;http://twitpic.com/show/thumb/#{id}.jpg\&quot;" alt="" /></a>"
    end
  end

  class YfrogAdapter
    def self.match?(url)
      !(url =~ /yfrog\.com/).nil?
    end

    def self.html(url, text)
      uri = URI.parse(url)
      id = uri.path
      "<a href="\&quot;#{url}\&quot;"><img title="\&quot;#{text}\&quot;/" src="\&quot;http://yfrog.com#{id}:small\&quot;" alt="" /></a>"
    end
  end

  class PlixiAdapter
    def self.match?(url)
      !(url =~ /plixi\.com/).nil?
    end

    def self.html(url, text)
      "<a href="\&quot;#{url}\&quot;"><img title="\&quot;#{text}\&quot;" src="\&quot;http://api.plixi.com/api/tpapi.svc/imagefromurl?size=small&url=#{url}\&quot;" alt="" /></a>"
    end
  end

  def self.for_users(users)
    tweets = fetch_tweets(users)
    build_pictures_html(tweets)
  end

  private

    def self.extract_urls(text, urls = [])
      url_regex =  Regexp.new('\b(([\w-]+://?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|/)))')
      url_regex.match(text) do |match|
        urls << match[0]
        extract_urls(match.post_match, urls)
      end
      urls
    end

    def self.build_pictures_html(tweets)
      pictures = {}
      tweets.each do |t|
        pictures_for_text = build_picture_html("from @#{t.from_user}: #{t.text}")
        pictures.merge!(pictures_for_text)
      end
      pictures.values
    end

    def self.fetch_tweets(users)
      @client = Twitter::Search.new
      users_query = users.join(' OR ')
      query = "http (#{users_query})"
      @client.containing(query).per_page(200)
    end

    def self.build_picture_html(text)
      adapters = [TwitpicAdapter, PlixiAdapter, YfrogAdapter]
      pictures = {}
      urls = extract_urls(text)
      urls.each do |url|
        adapters.each do |adapter|
          pictures[url] = adapter.html(url, text) if adapter.match? url
        end
      end
      pictures
    end
end

An example is on github if you want to read more.

More hard bits

Extracting instagr.am preview pictures is a little bit trickier. I will talk about how to do that in an upcoming blog post.

Life without IntelliSense in Ruby. Part 1: TextMate

IntelliSense is a feature in the Visual Studio IDE that saves you a hell of a lot of typing and makes APIs discoverable. A common question/concern/phobia among .NET developers is how can Ruby development be productive without this feature? I am currently making the transition to Ruby myself. I have found that the combination of the powerful features of TextMate, quick feedback from the IRB, and the Ruby language reduce your dependence on IntelliSense. In this first post I discuss TextMate.

TextMate

Some Ruby IDEs such as JetBrains RubyMine do have Visual Studio style IntelliSense, but the majority of Ruby developers use TextMate as their primary editor. This has two features that give you 50% of what IntelliSense does: Completion and Snippets. (Other popular Ruby editors such as Emacs and Vim have similar features.)

Completion

Completion works as follows: if you start typing a word and press the ESC key, TextMate will attempt to complete the word if it has appeared previously in the file. For example if I have a Ruby file like the following:

TextMate completion example

and I press ESC after the p, TextMate will expand the p to print, as print has appeared previously in the file

TextMate completion example

Pressing ESC again will cycle through other potential matches. This is simplistic compared to auto-complete in Visual Studio (as TextMate lacks any type information about the code) but it does save a lot of typing.

Snippets

Snippets generate commonly used Ruby and API structures quickly. Typically you type the first 3 letters of a structure and then press TAB. For example, pressing def and TAB will generate a function definition:

Method snippet example

You can install Bundles (TextMate extension packages) to add snippets for popular frameworks such as Rails or RSpec. For example, typing vl and TAB generates the following Rails code:
validates_length_of :attribute, :within => 3..20, :message => "must be present"

This is neat as it also helps you discover information about the API.

Other features

TextMate also has a help shortcut (CTRL + H) that looks up the API documenation on any method you highlight. Code Navigation is pretty powerful too. You can jump around files in a project (CMD + T) and between methods in a file (CMD + SHIFT + T) easily. RubyAMP is a powerful TextMate bundle that expands the Completion feature to include all the files in a project.

Next time

Next time I will discuss how the Ruby language itself, the APIs, and the IRB further lessen the dependence on IntelliSense.