lib/fbe/overwrite.rb



# frozen_string_literal: true

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

require_relative '../fbe'
require_relative 'fb'

# Overwrites a property in the fact by recreating the entire fact.
#
# If the property doesn't exist in the fact, it will be added. If it does
# exist, the entire fact will be destroyed, a new fact created with all
# existing properties, and the specified property set with the new value.
#
# It is important that the fact has the +_id+ property. If it doesn't,
# an exception will be raised.
#
# @param [Factbase::Fact] fact The fact to modify (must have _id property)
# @param [String] property The name of the property to set
# @param [Any] value The value to set (can be any type)
# @param [Factbase] fb The factbase to use (defaults to Fbe.fb)
# @return [Factbase::Fact] Returns new fact if recreated, or original if unchanged
# @raise [RuntimeError] If fact is nil, has no _id, or property is not a String
# @note This operation preserves all other properties during recreation
# @note If property already has the same single value, no changes are made
# @example Update a user's status
#   user = fb.query('(eq login "john")').first
#   updated_user = Fbe.overwrite(user, 'status', 'active')
#   # All properties preserved, only 'status' is set to 'active'
def Fbe.overwrite(fact, property, value, fb: Fbe.fb)
  raise 'The fact is nil' if fact.nil?
  raise 'The fb is nil' if fb.nil?
  raise "The property is not a String but #{property.class} (#{property})" unless property.is_a?(String)
  return fact if !fact[property].nil? && fact[property].size == 1 && fact[property].first == value
  before = {}
  fact.all_properties.each do |prop|
    before[prop.to_s] = fact[prop]
  end
  id = fact['_id']&.first
  raise 'There is no _id in the fact, cannot use Fbe.overwrite' if id.nil?
  raise "No facts by _id = #{id}" if fb.query("(eq _id #{id})").delete!.zero?
  n = fb.insert
  before[property.to_s] = [value]
  before.each do |k, vv|
    next unless n[k].nil?
    vv.each do |v|
      n.send(:"#{k}=", v)
    end
  end
  n
end