lib/fbe/just_one.rb



# frozen_string_literal: true

# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Zerocracy
# SPDX-License-Identifier: MIT

require 'others'
require 'time'
require_relative '../fbe'
require_relative 'fb'

# Ensures exactly one fact exists with the specified attributes in the factbase.
#
# This method creates a new fact if none exists with the given attributes,
# or returns an existing fact if one already matches. Useful for preventing
# duplicate facts while ensuring required facts exist.
#
# @example Creating or finding a unique fact
#   require 'fbe/just_one'
#   fact = Fbe.just_one do |f|
#     f.what = 'github_issue'
#     f.issue_id = 123
#     f.repository = 'zerocracy/fbe'
#   end
#   # Returns existing fact if one exists with these exact attributes,
#   # otherwise creates and returns a new fact
#
# @example Attributes are matched exactly (case-sensitive)
#   Fbe.just_one { |f| f.name = 'Test' }  # Creates fact with name='Test'
#   Fbe.just_one { |f| f.name = 'test' }  # Creates another fact (different case)
#
# @param [Factbase] fb The factbase to search/insert into (defaults to Fbe.fb)
# @yield [Factbase::Fact] Block to set attributes on the fact
# @return [Factbase::Fact] The existing or newly created fact
# @note System attributes (_id, _time, _version) are ignored when matching
def Fbe.just_one(fb: Fbe.fb)
  attrs = {}
  f =
    others(map: attrs) do |*args|
      k = args[0]
      if k.end_with?('=')
        @map[k[0..-2].to_sym] = args[1]
      else
        @map[k.to_sym]
      end
    end
  yield f
  q = attrs.except('_id', '_time', '_version').map do |k, v|
    vv = v.to_s
    if v.is_a?(String)
      vv = "'#{vv.gsub('"', '\\\\"').gsub("'", "\\\\'")}'"
    elsif v.is_a?(Time)
      vv = v.utc.iso8601
    end
    "(eq #{k} #{vv})"
  end.join(' ')
  q = "(and #{q})"
  before = fb.query(q).each.to_a.first
  return before unless before.nil?
  n = fb.insert
  attrs.each { |k, v| n.send(:"#{k}=", v) }
  n
end