frafferz/geek

# This is a comment

ActiveRecord::QueryCache and Rack Speed

Ignore this unless you’re using a Rack handler and ActiveRecord.

If you’re using a vanilla rack handler or Grape or JSONRPC2 or something similar that accesses your database via ActiveRecord, and you’re mounting it directly in Rack you’ll probably benefit from using the ActiveRecord::QueryCache. Unless you’re going through the rails stack, you don’t get this for free - you have to ask.

1
  use ActiveRecord::QueryCache

It’s just a standard piece of Rack Middleware, but it turns on DB caching for the duration of the request.

e.g.

1
2
3
4
5
map '/foo/bar' do
  use Rack::Logger
  use ActiveRecord::QueryCache # <-- this increased the speed of my API calls by ~20%
  run MyRackHandler
end

Ruby Utils

The #to_proc method is pretty useful - it allows all sorts of niceness, such as

1
[11,12,13].map(&:to_s) #=> ["11","12","13"]`

This works by calling Symbol#to_proc, which is defined something like:

1
2
3
def to_proc
  lambda { |object| object.send(self) }
end

But what about if you want to pass arguments to the function? Suppose you wanted to call #to_s(16) on each element in the array?

If you still want the compact formulation, you can add a [] method to the Symbol:

1
2
3
4
5
class Symbol
  def [](*args)
    lambda { |o| o.send(self, *args) }
  end
end

which means that you can use stuff like this:

1
2
[11,12,13].map(&:to_s[16]) #=> ["b","c","d"]`
[11,12,13].map(&:*[2]) #=> [22, 24, 26]

Method in the Madness: Ensure and Return

Was pondering the question: what code runs when method level rescue, else and ensure are used in ruby?

TL;DR summary

1
2
3
4
5
6
7
8
9
def some_method
  # main body
rescue
  # rescue code
else
  # alternative to rescue
ensure
  # always run me last
end
  1. Without return the last computed value that is not in the ensure block is returned (this will either be the main body, the rescue block or the else block).
  2. Using return in the main body of the method means that else block doesn’t run.
  3. Using return in an ensure block always overrides any other value returned by the method, regardless of whether any other section of the method also used the return keyword.
  4. Values from an ensure block are only ever returned when the return keyword is used.

Simple function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def value(sym)
  puts("ran #{sym}")
  sym
end

def fn1
  value(:fn1)
rescue
  value(:rescue1)
else
  value(:else1)
ensure
  value(:ensure1)
end

fn1() #=> :else1

Output:

ran fn1
ran else1
ran ensure1

Function with error:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def value(sym)
  puts("ran #{sym}")
  sym
end

def fn2
  raise value(:fn2)
rescue
  value(:rescue2)
else
  value(:else2)
ensure
  value(:ensure2)
end

fn2() #=> :rescue2

Output:

ran fn2
ran rescue2
ran ensure2

Function with return in main body

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def value(sym)
  puts("ran #{sym}")
  sym
end

def fn3
  return value(:fn3)
rescue
  value(:rescue3)
else
  value(:else3)
ensure
  value(:ensure3)
end

fn3() #=> :fn3

Output:

ran fn3
ran ensure3

Function with return in main body and return in ensure block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def value(sym)
  puts("ran #{sym}")
  sym
end

def fn4
  return value(:fn4)
rescue
  return value(:rescue4)
else
  return value(:else4)
ensure
  return value(:ensure4)
end

fn4() #=> :ensure4

Output:

ran fn4
ran ensure4

Function with return in main body and in ensure and error raised

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def value(sym)
  puts("ran #{sym}")
  sym
end

def fn5
  return raise value(:fn5)
rescue
  return value(:rescue5)
else
  return value(:else5)
ensure
  return value(:ensure5)
end

fn5() #=> :ensure5

Output:

ran fn5
ran rescue5
ran ensure5

Passenger Standalone on Ubuntu 11.10 (Oneiric Ocelot)

Until issue 704 is resolved, Passenger Standalone won’t compile properly on Ubuntu 11.10 (Oneirc Ocelot - currently pre-release) using the default settings.

To work around this, use GCC 4.4 instead. You’ll need to install gcc-4.4 and libstdc++6-4.4-dev and then specify GCC 4.4 at compile time using the CC environment variable.

1
2
$ sudo apt-get install gcc-4.4 libstdc++6-4.4-dev
$ CC=gcc-4.4 passenger start

Hopefully this will help anyone else who’s updated to the latest Ubuntu pre-release and still wants to use Passenger Standalone.

Playing With Zip - Russian Dolls

The standard answer is that zip files can’t contain more than one copy of a file without containing more than one copy of a file. In other words, there’s not a portable version of a *nix style hard link.

And that’s kind of true. However it is theoretically possible to create valid zip files that violate this principle in a platform independant manner. Unfortunately this doesn’t work properly with Stuffit :(

The data for a file entry must start immediately following the header, but the header can be upto ~65k and ends with fields that should be ignored if they are not understood. So we can stuff a local file header inside the end of a parent local file header (and prefix 32 bytes of “unknown” extra field) so that we have two valid local file headers that each end immediately before the only copy of the file data, as pictured:

Stuffing headers inside headers like Russian Dolls

And then we add the entries to Central Directory as if they were normal file entries.

Tests work fine with Info-ZIP, 7-Zip and the Windows built-in zip support. Unfortunately Stuffit on OS X only appears to recognise the “normal” entries (ie. doesn’t extract the embedded headers).

Playing With Zip

I wanted to stream zip files with lots of JPEGs in. Hundreds of JPEGs from digital cameras and, being as they were JPEGs, didn’t really care about trying to compress them any further.

  1. I wanted to create (potentially) huge archives. So I’d need something that supported ZIP64 extensions.
  2. I wanted to mix local files and files streamed from internal web servers.
  3. I wanted to create a zip file on the fly, with minimal buffering, to minimize disk and memory requirements.
  4. I wanted to support large numbers of simultaneous downloads.
  5. I also wanted (if possible) to efficiently include the same file more than once in an archive with different filenames.
  6. I wanted to continue to use zip archives.

Simples?

If only.

Streaming ZIP64 support (or lack thereof)

There are several ruby zip libraries, e.g. rubyzip, zip-ruby and archive-zip - but they seem to fall into two camps: pure ruby with no ZIP64 or wrapping a C library (e.g. libzip) but with no obvious way to create a zip file and start streaming it before it’s complete.

So I indulged my NIH syndrome reflex and wrote zip64writer which streams zip files and can automatically starts using ZIP64 extensions when needed. (I did look at adding ZIP64 support to rubyzip, but I figured fairly quickly that it would be easier to roll a specifically targetted library than adapt it to my needs.)

So writing a zip file to a stream works something like:

1
2
3
4
5
6
7
8
9
10
11
require 'zip64/writer'

File.open("output.zip", "wb") do |fp|
  Zip64::ZipWriter.new(fp) do |zip|
      File.open("sample.jpg", "rb") do |rfp|
          zip.add_entry(rfp,
                  :mtime => Time.now,
                  :name => 'myphoto.jpg')
      end
  end # Implicit close writes central directory to stream
end

ZIP64 extensions are extra header fields, and an extra couple of blocks at the end of the zip file, which allow zip files to contain more than 65,535 entries (the limit of a 16bit integer) & for the zip archives (and the files inside them) to be greater than 4 Gb (the limit of a 32bit integer) in size.

ASCII Art Diagram

The writer detects when an offset requires a 64bit integer (ie. offset >4Gb) and automatically starts using ZIP64 extensions - so the files are still as compatible as possible with old zip implementations that don’t support ZIP64 (e.g. Windows XP shell).

Basic testing reveals that ZIP64 files created this way (ie. a mix of standard encoding and ZIP64 encoding) work fine on Windows 7, OS X 10.5+. (Also the version of file-roller shipped with Lucid Lynx opens them fine, although the version of zip shipped with Hardy Heron is too old.)