IP Geolocation in Ruby on Rails: Detect Visitor Location
Ruby on Rails powers thousands of production web apps. Adding IP geolocation lets you personalize content, detect timezones, and log visitor geography. Here's how to do it with a free API — no gems required.
Basic Ruby: Net::HTTP
require "net/http"
require "json"
uri = URI("https://geo.kamero.ai/api/geo")
response = Net::HTTP.get(uri)
location = JSON.parse(response)
puts "IP: #{location['ip']}"
puts "City: #{location['city']}"
puts "Country: #{location['country']}"
puts "Timezone: #{location['timezone']}"With Error Handling
require "net/http"
require "json"
def get_geolocation
uri = URI("https://geo.kamero.ai/api/geo")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 3
http.read_timeout = 5
response = http.get(uri.path)
if response.code == "200"
JSON.parse(response.body)
else
nil
end
rescue StandardError => e
Rails.logger.error("Geolocation failed: #{e.message}")
nil
endRails Controller Concern
# app/controllers/concerns/geolocatable.rb
module Geolocatable
extend ActiveSupport::Concern
private
def visitor_location
@visitor_location ||= fetch_location
end
def visitor_country
visitor_location&.dig("country")
end
def visitor_timezone
visitor_location&.dig("timezone") || "UTC"
end
def fetch_location
cache_key = "geo:#{request.remote_ip}"
Rails.cache.fetch(cache_key, expires_in: 1.hour) do
uri = URI("https://geo.kamero.ai/api/geo")
response = Net::HTTP.get_response(uri)
response.code == "200" ? JSON.parse(response.body) : nil
end
rescue StandardError
nil
end
end
# Usage in any controller:
class HomeController < ApplicationController
include Geolocatable
def index
@city = visitor_location&.dig("city") || "there"
@country = visitor_country
end
endRails Middleware
# lib/middleware/geolocation.rb
class GeolocationMiddleware
def initialize(app)
@app = app
end
def call(env)
begin
uri = URI("https://geo.kamero.ai/api/geo")
response = Net::HTTP.get_response(uri)
if response.code == "200"
env["geo.location"] = JSON.parse(response.body)
end
rescue StandardError
# Silently fail
end
@app.call(env)
end
end
# config/application.rb
config.middleware.use GeolocationMiddleware
# Access in controllers:
# request.env["geo.location"]Using Faraday (Popular HTTP Client)
# Gemfile: gem "faraday"
require "faraday"
class GeoService
def self.lookup
conn = Faraday.new(url: "https://geo.kamero.ai") do |f|
f.request :json
f.response :json
f.options.timeout = 5
end
response = conn.get("/api/geo")
response.success? ? response.body : nil
rescue Faraday::Error => e
Rails.logger.warn("Geo lookup failed: #{e.message}")
nil
end
end
# Usage:
location = GeoService.lookup
puts location["city"] if locationBackground Job: Enrich User Records
# app/jobs/enrich_user_location_job.rb
class EnrichUserLocationJob < ApplicationJob
queue_as :default
def perform(user_id)
user = User.find(user_id)
location = GeoService.lookup
if location
user.update(
detected_country: location["country"],
detected_city: location["city"],
detected_timezone: location["timezone"]
)
end
end
end
# Trigger on sign-up:
after_create :enrich_location
def enrich_location
EnrichUserLocationJob.perform_later(id)
endView Helper
# app/helpers/geo_helper.rb
module GeoHelper
def local_time(time, format: :short)
tz = controller.try(:visitor_timezone) || "UTC"
time.in_time_zone(tz).to_fs(format)
end
def visitor_greeting
city = controller.try(:visitor_location)&.dig("city")
city ? "Welcome from #{city}!" : "Welcome!"
end
end