caching snippets

Geolocation with MaxMind's GeoIP and the geoip-city RubyGem

Tagged geoip, geolocation, maxmind, caching, rails  Languages ruby

Install GeoIP library

wget http://www.maxmind.com/download/geoip/api/c/GeoIP.tar.gz
tar -zxvf GeoIP.tar.gz
cd GeoIP
./configure --prefix=/opt/GeoIP
make && sudo make install

Install the geoip-city gem

gem install geoip_city -- --with-geoip-dir=/opt/GeoIP

Test the bindings

curl -O http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
gunzip GeoLiteCity.dat.gz

Fire up IRB and try the following code:

require 'rubygems'
require 'geoip_city'
db = GeoIPCity::Database.new('GeoLiteCity.dat')
result = db.look_up('192.143.34.23')
p result

Another option is to use hostip.info's database, as described in this article.

Create a wrapper

require 'rubygems'
require 'geoip_city'
require 'ostruct'

class Location < OpenStruct
end

class GeoIP
  class << self
    DB = GeoIPCity::Database.new('GeoLiteCity.dat')

    def lookup(ip)
      if result = DB.look_up(ip)
        location = Location.new

        #
        # {:country_code=>"FR", :country_code3=>"FRA", :country_name=>"France", :latitude=>46.0, :longitude=>2.0}
        #
        result.each do |key, val| 
          location.send "#{key}=", val
        end
      end

      location
    end
  end
end

Add some Rails caching

Combined with the above code this will give you cached IP lookups:

class GeoIP

  class << self
    def lookup_with_caching(ip)
      Rails.cache.fetch(ip, :expires_in => 1.month) do 
        lookup_without_caching(ip)
      end
    end

    alias_method_chain :lookup, :caching
  end
end

Alternatives

If you're unable to install the C extension you might want to have a look at the geoip gem, which is a pure Ruby library that can read the MaxMind's geoip database. It's slower but easier to install: http://geoip.rubyforge.org/

How to support conditional gets in Rails

Tagged caching, rails, last_modified, etag, fresh  Languages ruby

From the When to Tell Your Kids About Client Caching RailsConf presentation (PDF)

def fresh?(response)
def stale?(:etag => @object, :last_modified => updated_at.utc)
def not_modified?(modified_at)
def etag_matches?(etag)
class PeopleController < ApplicationController
    def show
        @person = Person.find(params[:id])
        response.last_modified = @person.updated_at.utc
        response.etag = @person 
        return head(:not_modified) if request.fresh?(response)

        respond_to do |wants|
            #...
        end
    end
end
response.etag = @person # => “5cb44721b6ce18857ff6900486dc4aba”
@person.cache_key # => "people/5-20071224150000"
class PeopleController < ApplicationController
    def show
        @person = Person.find(params[:id])
        if stale?(:etag => @person, :last_modified => @person.updated_at.utc)
            respond_to do |wants|
            #...
            end
        end
    end
end

How to set the Expires header with Apache 2 and mod_expires

Tagged apache, expires, mod_expires, header, caching  Languages apacheconf

First you need to enable the mod_expires module:

a2enmod expires

Next add this to your configuration:

ExpiresActive On
# Set Expires header to current time by default
ExpiresDefault A0

<FilesMatch "\.(flv|ico|pdf|avi|mov|ppt|doc|mp3|wmv|wav)$">
  ExpiresDefault "access plus 30 days"
</FilesMatch>

<FilesMatch "\.(jpg|jpeg|png|gif|swf|bmp|)$">
  ExpiresDefault "access plus 7 days"
</FilesMatch>

<FilesMatch "\.(txt|xml|js|css)$">
  ExpiresDefault "access plus 1 day"
</FilesMatch>

Now restart Apache:

$ sudo /etc/init.d/apache2 force-reload

Check that the proper headers are set with Firebug, Yahoo YSlow or Google Page speed.