miércoles, 25 de marzo de 2009

How to know a user location with Rails

Knowing the user location is useful not only for statics, but also, for example, to avoid that the same ip address votes twice or to allow that your site shows products dynamically in relation with the place where the user is and a lot of other applications.

The first task is to know the ip address of the user, to do this we can use a very helpful method of rails called remote_ip, this function is very smart, looks for in the headers HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR and REMOTE_ADDR and parse the value to figure out the ip address. So if we want to know the ip address we can use this:
@client_ip = request.remote_ip
If you are working in your local machine the remote_ip will be 127.0.0.1 and this address cannot be processed, then you have to write the following, instead the above code:
@client_ip = (RAILS_ENV == 'development') ? '201.231.22.125' : request.remote_ip

Once we have the ip address we have to figure out the place where the ip address is from, to do this we are going to use a free database of MaxMind, they offer geolocation databases, the free edition (GeoIpLite) is less accurate than the commercial version but it was enough for me, here is the link to the free binary database, I decompress the database in /opt/GeoIP/share/GeoIP/.

To work with the above database you have to install the C library geoip, I did this from the source but you can get it from synaptic, these are the commands that I used:

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

And to work from Ruby with this library I use geoip_city gem, to install it I used:

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

Well we have everything installed, now we can test it from the Rails side, in the root project open a console and run the following:

script/console
>> require 'geoip_city'
>> g = GeoIPCity::Database.new('/opt/GeoIP/share/GeoIP/GeoLiteCity.dat')
>> res = g.look_up('201.231.22.125')
=> {:latitude=>-33.13330078125, :country_code3=>"ARG", :longitude=>-64.3499984741211, :city=>"Río Cuarto", :country_name=>"Argentina", :country_code=>"AR", :region=>"05"}


If you get something like this you have everything working, but when I did my first test I got the following error message:
"libgeoip.so.1: cannot open shared object file: No such file or directory"
I solved it running: sudo ldconfig /etc/ld.so.conf

Once that we have everything working we can build our country method oracle, I did it in the application.rb file:

def get_country
@client_ip = request.remote_ip @client_ip = '201.231.22.125'

if session[:country].blank?
g = GeoIPCity::Database.new('/opt/GeoIP/share/GeoIP/GeoLiteCity.dat')
@user_location = g.look_up(@client_ip)

# if there isn't a country_code then the default country is USA
session[:country] = @user_location[:country_code] || 'US'
end
end


and "voila" you have in all the views and controllers the variable session[:country] that contains the current country location of the user, if you want you can be more accurate and ask for cities, lats, longs but I just neded the user country.

Well this was my first post, so I hope that this will be useful for somebody.

1 comentario:

  1. Te felicito por un primer post tan copado. Ya me colgue de tu RSS feed! :-)

    Saludos.

    ResponderEliminar