A Fresh Cup is Mike Gunderloy's software development weblog, covering Ruby on Rails and whatever else I find interesting in the universe of software. I'm a full-time Rails developer and contributor, available for long- or short-term consulting, with solid experience in working as part of a distributed team. If you'd like to hire me, drop me a line. I'm also the author of Rails Rescue Handbook and Rails Freelancing Handbook.

Navigation
« Double Shot #472 | Main | Double Shot #471 »
Wednesday
10Jun2009

Finding Visitor Locations in Rails

I've got an application that I work on where the client wants to track which countries they're seeing click-throughs from. They want this in real time and in the application's UI, not in an external package such as Google Analytics. There are various ways to guess at country, but for the purposes of this application, basing it on the IP address and where its range is assigned proves to be good enough.

There are various services that will let you geolocate based on IP just by making an API call. But there's no particular reason to go outside your own application for this; it's pretty trivial to set up in Rails:

1) Download the "Complete (Country) One Table" SQL file from IPInfoDB. This is the raw data necessary to do the lookups. Decompress it and then run the resulting SQL file in your application's database. This will give you an ip_group_country table. There's no need to set up a matching model; we're just going to hit it with raw SQL.

2) In this particular application, we're maintaining an Event model that holds, among other things, the IP address of the request. So the easy answer is to geolocate instances of that model when they're created or updated:

[sourcecode language='ruby']
class Event < ActiveRecord::Base
before_save :get_country_info

def get_country_info
segments = remote_ip.split('.')
ip_atom = ((segments[0].to_i * 256 + segments[1].to_i) * 256 + segments[2].to_i)*256
result = connection.execute("SELECT * FROM `ip_group_country` where `ip_start` <= #{ip_atom} order by ip_start desc limit 1;")
row = result.fetch_row
self.country_code = row[2]
self.country_name = row[3]
end

end
[/sourcecode]

3) There is no #3. That's it!

Reader Comments (5)

Or..

3) Update your DB whenever the IPInfoDB changes.

But at least they offer an RSS feed so you can know when it does change.

June 10, 2009 | Unregistered CommenterStephen Sykes

The way I do it is to use the Nginx GeoIP module and just append a header to the request containing the country name. It's worked out well so far.

See http://wiki.nginx.org/NginxHttpGeoIPModule

June 10, 2009 | Unregistered CommenterWesley Moxam

Excellent post!

It should be noted that you will need to refresh your data at regular intervals (monthly or quarterly) depending on the needs of your application. Also, if attempting to use this solution for preventing IPs from certain countries it's not 100% foolproof.

June 10, 2009 | Unregistered CommenterDennis Yarborough

Ruby has an IPAddr class in the standard library. Remember to require 'ipaddr' somewhere, and then simply:

ip_atom = IPAddr.new(remote_ip).to_i

June 12, 2009 | Unregistered Commenterdubek

dubek: Thanks for that! Some day I will learn Ruby, really.

Stephen, Dennis: Yes, the database changes, but the thrash isn't bad in the context of this application. We're not doing country-based blocking, just trying to give admins & clients an overall idea of where the traffic comes from. I plan to update the database monthly, when we do some other log-file related analysis.

June 12, 2009 | Unregistered CommenterMike Gunderloy

PostPost a New Comment

Enter your information below to add a new comment.

My response is on my own website »
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>