40

I want to extract a user country name from visitors' IP addresses.

I could get the IP address with remote_ip. But what could be the easiest way to get the country name?

It doesn't have to be super accurate. Any ruby library (gem or plugin) to do this?

I want an simple and easy solution for this.

Jon Seigel
  • 12,251
  • 8
  • 58
  • 92
TK.
  • 27,073
  • 20
  • 64
  • 72

11 Answers11

51

You can use geoip gem.

environment.rb

config.gem 'geoip'

Download GeoIP.dat.gz from http://www.maxmind.com/app/geolitecountry. unzip the file. The below assumes under #{RAILS_ROOT}/db dir.

@geoip ||= GeoIP.new("#{RAILS_ROOT}/db/GeoIP.dat")    
remote_ip = request.remote_ip 
if remote_ip != "127.0.0.1" #todo: check for other local addresses or set default value
  location_location = @geoip.country(remote_ip)
  if location_location != nil     
    @model.country = location_location[2]
  end
end
Chandra Patni
  • 17,347
  • 10
  • 55
  • 65
35

You can also use "Geocoder"

It will just make you life easier. Put the following line in your Gemfile and issue the bundle install command

gem 'geocoder'

Using this Gem, you can easily get the country, ip or even city of the originating IP. See an example below

request.ip  # =>    "182.185.141.75"
request.location.city   # =>    ""
request.location.country    # =>    "Pakistan"
Nadeem Yasin
  • 4,493
  • 3
  • 32
  • 41
  • Is there a limit with this gem? What type of provider you use with this gem? I have understood that there are limitations of 2500 request per day. Please correct me if I'm wrong. Thank you!. The documentation is a bit confusing. – hyperrjas Feb 24 '13 at 11:03
  • @hyperrjas yes if you're using Google's API you can checkout the the quota per each provider from this link https://github.com/alexreisner/geocoder#listing-and-comparison – mohamagdy Mar 10 '13 at 00:35
11

I'm using this one-liner:

locale = Timeout::timeout(5) { Net::HTTP.get_response(URI.parse('http://api.hostip.info/country.php?ip=' + request.remote_ip )).body } rescue "US"
stef
  • 14,172
  • 2
  • 48
  • 70
  • Perfect answer. Thanks a lot! I have also added something like `locale = 'US' if locale == 'XX'`. In some cases api.hostip.info returned `XX`, so I am catching that as well. – Tobias Feb 03 '14 at 22:15
  • 1
    Looking back at this, there is the danger of that service timing out, so you should probably put this onto an asynchronous call and send it back to the browser via a websocket or by polling. And cache the result in something like memcache or redis. – stef Feb 05 '14 at 12:22
  • Just tested with my IP and It gives a wrong result. – MIA Mar 15 '22 at 09:57
6

The simplest is to use an existing web service for this.

There are plugins that let you do much more, including making your models geolocation-aware (geokit-rails) automatically, but if all you need is a country code for example, simply sending an HTTP Get to http://api.hostip.info/country.php (there are other services but this one does not require an API key) will return it, e.g. :

Net::HTTP.get_response(URI.parse('http://api.hostip.info/country.php'))
=> US


Or polling http://api.hostip.info/ will return a full XML response with city, latitude, longitude, etc.

Be aware that the results you get are not 100% accurate. For example, right now I'm in France but reported as in Germany. This will be the case for pretty much any IP-based service.

JRL
  • 76,767
  • 18
  • 98
  • 146
  • You can pass the IP address of the visiting user to this API as a parameter http://api.hostip.info/?ip=217.132.3.54 which means you can do Net::HTTP.get_response(URI.parse('http://api.hostip.info/country.php?ip=' + request.remote_ip )) (Not sure why that ; character is appearing and I can't get rid of it) – stef Feb 03 '12 at 09:44
  • 1
    Yeah this seems like the simplest option. Another API you could use is http://ipinfo.io, which will return full JSON details if you request just that, or you can get just the country string with http://ipinfo.io/country – Ben Dowling Jun 29 '14 at 23:44
  • @BenDowling +1 for [ipinfo.io](http://ipinfo.io/) - I just tested a dozen real-world examples and hostip.info failed most while ipinfo.io didn't miss. Very reasonable prices/rate limits too. – Yarin Jun 23 '15 at 22:25
6

The geoip gem no longer works with the new MaxMind databases. There is a new gem that does this, MaxMind-DB-Reader-ruby.

Simply download the City or Country binary, gzipped databases from MaxMind, unzip, and use the following sort of code:

require 'maxmind/db'

reader = MaxMind::DB.new('GeoIP2-City.mmdb', mode: MaxMind::DB::MODE_MEMORY)

# you probably want to replace 1.1.1.1 with  request.remote_ip
# or request.env['HTTP_X_FORWARDED_FOR']
ip_addr = '1.1.1.1'
record = reader.get(ip_addr)
if record.nil?
  puts '#{ip_addr} was not found in the database'
else
  puts record['country']['iso_code']
  puts record['country']['names']['en']
end

reader.close

Adapt based on your needs. I created a method in an initializer which I can call as necessary.

Artelius
  • 48,337
  • 13
  • 89
  • 105
  • This is the Maxmind official gem / repo for their stuff, so the database should stay current as well, thanks Artelius :) – Kem Mason Oct 09 '19 at 17:05
5

One service you could use to do this is my own, https://ipinfo.io. It gives you country code and a bunch of other details:

$ curl ipinfo.io
{
  "ip": "24.6.61.239",
  "hostname": "c-24-6-61-239.hsd1.ca.comcast.net",
  "city": "Mountain View",
  "region": "California",
  "country": "US",
  "loc": "37.3845,-122.0881",
  "org": "AS7922 Comcast Cable Communications, LLC",
  "postal": "94040"
}

If you just want the country you can get that by requesting /country

$ curl ipinfo.io/country
US

You can then map from a country code to a name using the data from http://country.io, or using the example at http://ipinfo.io/developers/full-country-names

Ben Dowling
  • 17,187
  • 8
  • 87
  • 103
  • `%x(curl 'ipinfo.io/country')` is the right code to use. Does it return the server's country or the client's country? Are there quota limits or other limitations to respect? – Asarluhi Aug 04 '17 at 14:50
  • 1
    If you're calling it from the server it'll return the server's IP - you need to pass in the client's IP. See https://ipinfo.io/developers – Ben Dowling Aug 04 '17 at 16:30
2

I've just published a gem for the IPLocate.io API which I created.

Super easy, no databases to download, and 1,500 free requests per day:

require 'iplocate'

# Look up an IP address
results = IPLocate.lookup("8.8.8.8")

# Or with an API key
results = IPLocate.lookup("8.8.8.8", "abcdef")

results["country"]
# "United States"

results["country_code"]
# "US"

results["org"]
# "Google LLC"

results.inspect
# {
#   "ip"=>"8.8.8.8",
#   "country"=>"United States",
#   "country_code"=>"US",
#   "city"=>nil,
#   "continent"=>"North America",
#   "latitude"=>37.751,
#   "longitude"=>-97.822,
#   "time_zone"=>nil,
#   "postal_code"=>nil,
#   "org"=>"Google LLC",
#   "asn"=>"AS15169"
# }  

No Gem

It can also be used without a gem by just using Ruby's Net::HTTP and URI:

response = Net::HTTP.get( URI.parse( "https://www.iplocate.io/api/lookup/8.8.8.8" ) )

The request will return JSON so you can parse it and access it as follows:

country = JSON.parse( response )["country"]
# => "US"
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
ttarik
  • 3,824
  • 1
  • 32
  • 51
  • 2
    Great little service! Hooked it up in Ruby without needing to even download the gem. Just do a Net::HTTP request with `JSON.parse( Net::HTTP.get( URI.parse( "https://www.iplocate.io/api/lookup/139.18.2.74" ) ) )` – Joshua Pinter Mar 18 '18 at 02:16
  • @ttarik when I run your code I get the error: `require': cannot load such file -- ip address (LoadError)....I downloaded the iplocate gem. Do you have a possible solution to this? – tee Jun 24 '18 at 19:06
  • @tee Sounds like it can't find the gem. Have you added `iplocate` to your Gemfile and run `bundle install`? Or, you can use the 'No Gem' code example helpfully posted by Joshua :) – ttarik Jun 24 '18 at 22:43
  • @ttarik I added the gem and still get the error...it's all good though I can use the 'No Gem' code, thanks! – tee Jun 25 '18 at 11:10
1

You can try Yandex locator gem, service returns longitude, latitude and precision.

conn = YandexLocator::Client.new
result = conn.lookup(ip: "109.252.52.39")
# => {"position"=>{"altitude"=>0.0, "altitude_precision"=>30.0, "latitude"=>55.75395965576172, "longitude"=>37.62039184570312, "precision"=>100000.0, "type"=>"ip"}}
Sergey Chechaev
  • 562
  • 7
  • 22
1

Here's a Ruby example calling the ipdata.co API.

It's fast and has reliable performance thanks to having 10 global endpoints each able to handle >10,000 requests per second!

This answer uses a 'test' API Key that is very limited and only meant for testing a few calls. Signup for your own Free API Key and get up to 1500 requests daily for development.

Replace 78.8.53.5 with any ip you want to look up

require 'rubygems' if RUBY_VERSION < '1.9'
require 'rest_client'

headers = {
  :accept => 'application/json'
}

response = RestClient.get 'https://api.ipdata.co/78.8.53.5?api-key=test', headers
puts response

That'd give you

{
    "ip": "78.8.53.5",
    "is_eu": true,
    "city": "G\u0142og\u00f3w",
    "region": "Lower Silesia",
    "region_code": "DS",
    "country_name": "Poland",
    "country_code": "PL",
    "continent_name": "Europe",
    "continent_code": "EU",
    "latitude": 51.6557,
    "longitude": 16.089,
    "asn": "AS12741",
    "organisation": "Netia SA",
    "postal": "67-200",
    "calling_code": "48",
    "flag": "https://ipdata.co/flags/pl.png",
    "emoji_flag": "\ud83c\uddf5\ud83c\uddf1",
    "emoji_unicode": "U+1F1F5 U+1F1F1",
    "carrier": {
        "name": "Netia",
        "mcc": "260",
        "mnc": "07"
    },
    "languages": [
        {
            "name": "Polish",
            "native": "Polski"
        }
    ],
    "currency": {
        "name": "Polish Zloty",
        "code": "PLN",
        "symbol": "z\u0142",
        "native": "z\u0142",
        "plural": "Polish zlotys"
    },
    "time_zone": {
        "name": "Europe/Warsaw",
        "abbr": "CEST",
        "offset": "+0200",
        "is_dst": true,
        "current_time": "2018-08-29T15:34:23.518065+02:00"
    },
    "threat": {
        "is_tor": false,
        "is_proxy": false,
        "is_anonymous": false,
        "is_known_attacker": false,
        "is_known_abuser": false,
        "is_threat": false,
        "is_bogon": false
    },
}
Jonathan
  • 10,792
  • 5
  • 65
  • 85
  • when I run the above code I get the error: "`require': cannot load such file -- rest_client (LoadError)"....From where can I access the rest_client file – tee Jun 24 '18 at 18:51
1

Try the IP2Location Ruby

https://github.com/ip2location/ip2location-ruby

Pre-requisite

Download the free LITE database from http://lite.ip2location.com/ and use below.

Install

gem install ip2location_ruby

Usage

require 'ip2location_ruby'

i2l = Ip2location.new.open("./data/IP-COUNTRY-SAMPLE.BIN")
record = i2l.get_all('8.8.8.8')

print 'Country Code: ' + record.country_short + "\n"
print 'Country Name: ' + record.country_long + "\n"
print 'Region Name: ' + record.region + "\n"
print 'City Name: ' + record.city + "\n"
print 'Latitude: '
print record.latitude
print "\n"
print 'Longitude: '
print record.longitude
print "\n"
print 'ISP: ' + record.isp + "\n"
print 'Domain: ' + record.domain + "\n"
print 'Net Speed: ' + record.netspeed + "\n"
print 'Area Code: ' + record.areacode + "\n"
print 'IDD Code: ' + record.iddcode + "\n"
print 'Time Zone: ' + record.timezone + "\n"
print 'ZIP Code: ' + record.zipcode + "\n"
print 'Weather Station Code: ' + record.weatherstationname + "\n"
print 'Weather Station Name: ' + record.weatherstationcode + "\n"
print 'MCC: ' + record.mcc + "\n"
print 'MNC: ' + record.mnc + "\n"
print 'Mobile Name: ' + record.mobilebrand + "\n"
print 'Elevation: '
print record.elevation
print "\n"
print 'Usage Type: ' + record.usagetype + "\n"
Vlam
  • 1,622
  • 1
  • 8
  • 17
1

The gem geoip can be replaced with the new gem maxminddb. It support new MaxMind db format.

db = MaxMindDB.new('./GeoLite2-City.mmdb')
ret = db.lookup('74.125.225.224')

ret.found? # => true
ret.country.name # => 'United States'
ret.country.name('zh-CN') # => '美国'
ret.country.iso_code # => 'US'
ret.city.name(:fr) # => 'Mountain View'
ret.subdivisions.most_specific.name # => 'California'
ret.location.latitude # => -122.0574
Pioz
  • 6,051
  • 4
  • 48
  • 67