Secrets of the Standard Library - Paul Battley

None of these things are actually secrets, but Paul pointed out that he's been programming Ruby for quite some time, and only discovered some of the following relatively recently.

Diving straight in, what have we got...

Forwardable

Let's say we have an event, represented as an ActiveRecord object...

class Event < ActiveRecord::Base
  belongs_to :opportunity
end

...and an Opportunity. If an Event has an Opportunity and an Opportunity has an Organization, you'll get the following methods on your objects:

opportunity.organization
event.opportunity

That means you could call:

event.opportunity.organization

Hold on though, it's bad form. We've just violated the law of Demeter (the gist of which is that you can play with your friends, but you shouldn't mess with your friends' friends). You could write a wrapper method on the Event class that would return organization for you, or you could use forwardable:

require "forwardable"

class Event < ActiveRecord:: Base
  extend Forwardable
  belongs_to :opportunity

  def_delegators :opportunity, :organization

  def_delegator :opportunity, :organization, :opporunity_organization
end

open

You can use open to open a file, or (if you require open-uri) you can grab stuff off the net:

sample = open("http://google.co.uk") do |f|
  puts f.read(1024)
end

You can also use open to send all the output of your program through less:

open("|less", "w") do |f|
  80.times do |i|
    f.puts "Line #{i}"
  end
end

Here's a neat trick; use a pager if your program is used interactively:

if STDOUT.tty?
  $stdout = open("|less", "w")
end

# send stuff to STDOUT

$stdout.close

Array#assoc and rassoc

These methods are handy for looking up stuff inside nested data structures:

data = [["cat", "kitten", "feline"],
        ["dog", "puppy", "caine"],
        ["bear", "cub", "ursine"]]

data.assoc("dog") # -> ["dog", "puppy", "caine"]
data.rassoc("kitten") # -> ["cat", "kitten", "feline"]

pack/unpack

unpack works on a string. You can do really clever stuff, such as reading a QT/MPEG4 atom header:

length, name = @io.read(8).unpack("Na4")

"N" means that you should read 4 bytes in big endian order. "a" Means you should read ascii text. 4 means you should read 4 of thing represented by the previous character.

You can pack it all back up again to produce binary output:

header = [1234, "moov"].pack("Na4")

You can also do unicode:

codepoints = "string".unpack("U*")
=> [115, 116, 114, 105, 110, 103]

The asterisk means "any number of", so you get any number of code points back from your string.

So if you know a code point you can get the right character, without knowing how to type it on your keyboard:

em_dash = [0x2014].pack("U")  # emdash, apparently

Set

A set only keeps one copy of everything that you put in it, so it's a great way to track unique items. I liked this code for finding the set of vowel sequences that occur in a list of words:

Finding sequences with Ruby

This should come as no surprise, but you can also run set operations on your set objects:

require "set"

a = Set.new([:a, :b, :c])
b = Set.new([:a, :b])

a | b  # union of the two sets
a & b  # intersection (i.e. which items occur in both)

Soap (yuk)

If you have to do SOAP you want to do it in a way that makes it as easy as possible. It's pretty straightforward with the soap/wsdlDriver in the standard library.

pp

pp stands for "pretty print". It's a nice way to get a clean dump of a data structure in irb.

Cool stuff. I need to spend some time getting my head around pack/unpack...

More talks from Ruby Manor