Change summary
lib/area_code_repo.rb | 25 +++++++++++++++++++++++++
lib/geo_code.rb | 24 ++++++++++++++++++++++++
lib/geo_code_repo.rb | 37 +++++++++++++++++++++++++++++++++++++
3 files changed, 86 insertions(+)
Detailed changes
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require "json"
+
+require_relative "geo_code_repo"
+
+class AreaCodeRepo
+ def initialize(db: DB, geo_code_repo: GeoCodeRepo.new)
+ @db = db
+ @geo_code_repo = geo_code_repo
+ end
+
+ def find(q, limit: 3)
+ @geo_code_repo.find(q).then { |geo|
+ @db.query_defer(<<~SQL, [geo.country, geo.sql_point, limit])
+ SELECT area_code FROM area_codes
+ WHERE country=$1
+ ORDER BY location <-> $2
+ LIMIT $3
+ SQL
+ }.then { |rows|
+ rows.map { |row| row["area_code"] }
+ }
+ end
+end
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require "value_semantics/monkey_patched"
+
+class GeoCode
+ def self.for(data)
+ new(data)
+ end
+
+ def initialize(data)
+ @data = data
+ end
+
+ def country
+ return "US" unless @data["postal"]
+ return "US" if @data["postal"] =~ /\A\d+\Z/
+
+ "CA"
+ end
+
+ def sql_point
+ "POINT(#{'%.10f' % @data['longt']} #{'%.10f' % @data['latt']})"
+ end
+end
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require "cbor"
+require "json"
+
+require_relative "geo_code"
+
+class GeoCodeRepo
+ def initialize(memcache: MEMCACHE)
+ @memcache = memcache
+ end
+
+ def find(q)
+ cache(q) {
+ EM::HttpRequest.new(
+ "https://geocoder.ca/",
+ tls: { verify_peer: true }
+ ).aget(query: { json: 1, locate: q }).then { |res|
+ JSON.parse(res.response)
+ }
+ }.then(&GeoCode.method(:for))
+ end
+
+protected
+
+ def cache(k, &blk)
+ k = "geocode_#{k.gsub(/ /, '%20')}"
+ promise = EMPromise.new
+ @memcache.get(k, &promise.method(:fulfill))
+ promise.then { |cbor|
+ CBOR.decode(cbor)
+ }.catch(&blk).then { |result|
+ @memcache.set(k, result.to_cbor)
+ result
+ }
+ end
+end