Wednesday
10Jun2009
Finding Visitor Locations in Rails
Wednesday, June 10, 2009 at 6:43AM
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
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!
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.
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
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.
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
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.