From b35d4926b19b8a6315f63989f1b689bdf5625e97 Mon Sep 17 00:00:00 2001 From: Yessiest Date: Sun, 8 Oct 2023 23:22:39 +0400 Subject: [PATCH] Added SELECT object mapping, convenience methods, fixed weakref usage after object death --- lib/lpgar.rb | 55 +++++++++++++++++++++++++++++++++++++++++---------- lpgar.gemspec | 2 +- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/lib/lpgar.rb b/lib/lpgar.rb index f7c29f4..9a05dac 100644 --- a/lib/lpgar.rb +++ b/lib/lpgar.rb @@ -65,20 +65,53 @@ module LPGAR # Return either an existing Record or create a new one. # @param data [Hash{String => Object}] row data - def new(data) + # @param syncless [Boolean] do not perform any communication with PG + # @return [Object] new record object + def new(data, syncless: false) check_instantiable ident = Digest::MD5.hexdigest(data.values_at(*cols_pk).join) - if @instances[ident] + if @instances[ident] and @instances[ident].weakref_alive? @instances[ident].sync @instances[ident] end new_record = super(data) - create(new_record) unless new_record.sync + unless syncless + synced = new_record.sync + insert_query(new_record) unless synced + end track_instance(new_record) new_record end + # Explicitly create a new record. Returns nil if exists. + # @param data [Hash{String => Object}] row data + # @return [(Object,nil)] new record object or nil if exists + def create(data) + instance = new(data, syncless: true) + return if instance.sync + + insert_query(instance) + instance + end + + # Explicitly request an existing record. Returns nil if doesn't exist. + # @param data [Hash{String => Object}] row data + # @return [(Object,nil)] new record object or nil if doesn't exist. + def get(data) + instance = new(data, syncless: true) + instance if instance.sync + end + + # Map returned rows from a query to an array of objects of this table. + # @param query [String] raw postgresql query + # @return [Array(Object)] array of records of this table + def map(query) + conn.exec(query).map do |row| + new(row, syncless: true) + end + end + # Change row identity # Should not be called directly in most cases. # @param original [String] original row identity @@ -91,15 +124,15 @@ module LPGAR # Create sync query. # @return [String] def sync_query - return @_memo_query if @_memo_query + return @sync_query if @sync_query - selector = @cols_pk.map.with_index do |pk, index| - "#{pk} = $#{index + 1}" + selector = @cols_pk.map.with_index do |key, index| + "#{key} = $#{index + 1}" end.join " AND " - @_memo_query = <<~QUERY + @sync_query = <<~QUERY SELECT * FROM #{@table_name} WHERE #{selector} - LIMIT 1 + LIMIT 1; QUERY end @@ -139,6 +172,8 @@ module LPGAR end # rubocop:enable Metrics/MethodLength, Metrics/AbcSize + private + # Check if Record is properly set up. # @raise [StandardError] rasied if class is not properly set up def check_instantiable @@ -166,7 +201,7 @@ module LPGAR end # Create a new record by "INSERT" - def create(record) + def insert_query(record) recbody = record.instance_variable_get("@data") # @sg-ignore conn.exec(<<~QUERY, recbody.values) @@ -336,7 +371,7 @@ module LPGAR @conn = conn @instances = {} end - new_class.class_exec(&block) + new_class.class_exec(&block) if block new_class end diff --git a/lpgar.gemspec b/lpgar.gemspec index b86e4f6..1459479 100644 --- a/lpgar.gemspec +++ b/lpgar.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |spec| spec.name = "lpgar" - spec.version = "0.1" + spec.version = "0.2" spec.summary = "Lightweight Postgres Active Record" spec.description = <<~DESC Lightweight implementation of Active Record pattern for Postgres.