require 'bundler/setup'
require 'otto'
puts 'Simple Custom Geo Resolution'
puts '-' * 40
custom_resolver = lambda do |ip, _env|
case ip
when '1.2.3.4' then 'US'
when '5.6.7.8' then 'GB'
else nil end
end
Otto::Privacy::GeoResolver.custom_resolver = custom_resolver
puts "1.2.3.4 -> #{Otto::Privacy::GeoResolver.resolve('1.2.3.4', {})}"
puts "8.8.8.8 -> #{Otto::Privacy::GeoResolver.resolve('8.8.8.8', {})} (fallback)"
Otto::Privacy::GeoResolver.custom_resolver = nil
puts "\nAPI Integration with Caching"
puts '-' * 40
class CachedGeoAPI
def initialize(api_key)
@api_key = api_key
@cache = {}
end
def call(ip, _env)
return @cache[ip] if @cache.key?(ip)
country = mock_api_call(ip)
@cache[ip] = country
country
rescue StandardError => e
puts "API failed: #{e.message}"
nil end
private
def mock_api_call(ip)
case ip
when /^1\./ then 'US'
when /^2\./ then 'GB'
end
end
end
api_resolver = CachedGeoAPI.new('your_api_key')
Otto::Privacy::GeoResolver.custom_resolver = api_resolver
puts "1.2.3.4 -> #{Otto::Privacy::GeoResolver.resolve('1.2.3.4', {})}"
puts "1.2.3.4 -> #{Otto::Privacy::GeoResolver.resolve('1.2.3.4', {})} (cached)"
Otto::Privacy::GeoResolver.custom_resolver = nil
puts "\nPerformance Tips"
puts '-' * 40
puts '• Cache API results to avoid repeated calls'
puts "• Return nil from custom resolver to use Otto's fast fallback"
puts '• Use CloudFlare headers when available (fastest)'
puts '• Consider async/background geo updates for heavy traffic'
puts "\nProduction Pattern: Valkey/Redis Bloom Filters"
puts '-' * 40
puts 'For high-traffic applications, consider Bloom/Cuckoo filters:'
puts '• Store RIR IP prefixes in Valkey/Redis Bloom filter per country'
puts '• Memory: ~1MB for entire IPv4 table at 1% false positive rate'
puts '• Lookup: O(1) microsecond-level performance via BF.EXISTS'
puts '• Zero external dependencies, rebuild nightly from public RIR files'
puts '• Perfect for CDN header fallback when requests bypass edge'
puts ''