Postgres#query_one

Stephen Paul Weber created

Helper for queries that only return a single row.

Change summary

lib/call_attempt_repo.rb   |  9 ++++-----
lib/customer_plan.rb       |  6 +-----
lib/customer_repo.rb       |  8 +-------
lib/postgres.rb            | 18 ++++++++++++++++++
lib/trust_level_repo.rb    |  6 +++---
sgx_jmp.rb                 |  6 ++----
test/test_customer_info.rb | 12 ++++++------
test/test_helper.rb        |  6 ++++++
8 files changed, 41 insertions(+), 30 deletions(-)

Detailed changes

lib/call_attempt_repo.rb 🔗

@@ -49,25 +49,24 @@ protected
 	end
 
 	def find_usage(customer_id)
-		promise = db.query_defer(<<~SQL, [customer_id])
+		db.query_one(<<~SQL, customer_id, default: { a: 0 }).then { |r| r[:a] }
 			SELECT COALESCE(SUM(charge), 0) AS a FROM cdr_with_charge
 			WHERE
 				customer_id=$1 AND
 				start > DATE_TRUNC('month', LOCALTIMESTAMP)
 		SQL
-		promise.then { |rows| rows.first&.dig("a") || 0 }
 	end
 
 	def find_rate(plan_name, other_tel, direction)
-		promise = db.query_defer(<<~SQL, [plan_name, other_tel, direction])
+		promise = db.query_one(<<~SQL, plan_name, other_tel, direction)
 			SELECT rate FROM call_rates
 			WHERE
 				plan_name=$1 AND
 				$2 LIKE prefix || '%' AND
 				direction=$3
 			ORDER BY prefix DESC
-			LIMIT 1;
+			LIMIT 1
 		SQL
-		promise.then { |rows| rows.first&.dig("rate") }
+		promise.then { |row| row&.dig(:rate) }
 	end
 end

lib/customer_plan.rb 🔗

@@ -83,15 +83,11 @@ class CustomerPlan
 	end
 
 	def activation_date
-		dates = DB.query_defer(<<~SQL, [@customer_id])
+		DB.query_one(<<~SQL, @customer_id).then { |r| r[:start_date] }
 			SELECT
 				MIN(LOWER(date_range)) AS start_date
 			FROM plan_log WHERE customer_id = $1;
 		SQL
-
-		dates.then do |r|
-			r.first["start_date"]
-		end
 	end
 
 protected

lib/customer_repo.rb 🔗

@@ -150,16 +150,10 @@ protected
 		WHERE customer_id=$1 LIMIT 1
 	SQL
 
-	def fetch_sql(customer_id)
-		@db.query_defer(SQL, [customer_id]).then do |rows|
-			rows.first&.transform_keys(&:to_sym) || {}
-		end
-	end
-
 	def fetch_all(customer_id)
 		EMPromise.all([
 			@sgx_repo.get(customer_id),
-			fetch_sql(customer_id),
+			@db.query_one(SQL, customer_id, default: {}),
 			fetch_redis(customer_id)
 		]).then { |sgx, sql, redis| [sgx, sql.merge(redis)] }
 	end

lib/postgres.rb 🔗

@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require "delegate"
+
+class Postgres < SimpleDelegator
+	def self.connect(**kwargs)
+		new(PG::EM::ConnectionPool.new(**kwargs) { |conn|
+			conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
+			conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
+		})
+	end
+
+	def query_one(sql, *args, field_names_as: :symbol, default: nil)
+		query_defer(sql, args).then do |rows|
+			rows.field_names_as(field_names_as)&.first || default
+		end
+	end
+end

lib/trust_level_repo.rb 🔗

@@ -14,11 +14,11 @@ class TrustLevelRepo
 		EMPromise.all([
 			redis.get("jmp_customer_trust_level-#{customer.customer_id}"),
 			fetch_settled_amount(customer.customer_id)
-		]).then do |(manual, rows)|
+		]).then do |(manual, row)|
 			TrustLevel.for(
 				manual: manual,
 				plan_name: customer.plan_name,
-				**(rows.first&.transform_keys(&:to_sym) || {})
+				**row
 			)
 		end
 	end
@@ -26,7 +26,7 @@ class TrustLevelRepo
 protected
 
 	def fetch_settled_amount(customer_id)
-		db.query_defer(<<~SQL, [customer_id])
+		db.query_one(<<~SQL, customer_id, default: {})
 			SELECT SUM(amount) AS settled_amount FROM transactions
 			WHERE customer_id=$1 AND settled_after < LOCALTIMESTAMP AND amount > 0
 		SQL

sgx_jmp.rb 🔗

@@ -92,6 +92,7 @@ require_relative "lib/low_balance"
 require_relative "lib/port_in_order"
 require_relative "lib/payment_methods"
 require_relative "lib/paypal_done"
+require_relative "lib/postgres"
 require_relative "lib/registration"
 require_relative "lib/transaction"
 require_relative "lib/tel_selections"
@@ -220,10 +221,7 @@ when_ready do
 	REDIS = EM::Hiredis.connect
 	TEL_SELECTIONS = TelSelections.new
 	BTC_SELL_PRICES = BTCSellPrices.new(REDIS, CONFIG[:oxr_app_id])
-	DB = PG::EM::ConnectionPool.new(dbname: "jmp") { |conn|
-		conn.type_map_for_results = PG::BasicTypeMapForResults.new(conn)
-		conn.type_map_for_queries = PG::BasicTypeMapForQueries.new(conn)
-	}
+	DB = Postgres.connect(dbname: "jmp")
 
 	DB.hold do |conn|
 		conn.query("LISTEN low_balance")

test/test_customer_info.rb 🔗

@@ -20,9 +20,9 @@ class CustomerInfoTest < Minitest::Test
 		sgx.expect(:registered?, false)
 
 		CustomerPlan::DB.expect(
-			:query_defer,
-			EMPromise.resolve([{ "start_date" => Time.now }]),
-			[String, ["test"]]
+			:query_one,
+			EMPromise.resolve({ start_date: Time.now }),
+			[String, "test"]
 		)
 
 		cust = customer(sgx: sgx, plan_name: "test_usd")
@@ -39,9 +39,9 @@ class CustomerInfoTest < Minitest::Test
 		sgx.expect(:fwd, fwd)
 
 		CustomerPlan::DB.expect(
-			:query_defer,
-			EMPromise.resolve([{ "start_date" => Time.now }]),
-			[String, ["test"]]
+			:query_one,
+			EMPromise.resolve({ start_date: Time.now }),
+			[String, "test"]
 		)
 
 		cust = customer(sgx: sgx, plan_name: "test_usd")

test/test_helper.rb 🔗

@@ -228,6 +228,12 @@ class FakeDB
 	def query_defer(_, args)
 		EMPromise.resolve(@items.fetch(args, []).to_a)
 	end
+
+	def query_one(_, *args, field_names_as: :symbol, default: nil)
+		row = @items.fetch(args, []).to_a.first
+		row = row.transform_keys(&:to_sym) if row && field_names_as == :symbol
+		EMPromise.resolve(row || default)
+	end
 end
 
 class FakeLog