diff --git a/lib/fbe/overwrite.rb b/lib/fbe/overwrite.rb index b3f7fbb2..b2f54a05 100644 --- a/lib/fbe/overwrite.rb +++ b/lib/fbe/overwrite.rb @@ -54,12 +54,19 @@ def Fbe.overwrite(fact, property_or_hash, values = nil, fb: Fbe.fb, fid: '_id') end return unless modified unless overwrites - property_or_hash.each do |k, vv| - sk = k.to_s - next unless fact[sk].nil? - vv = [vv] unless vv.is_a?(Array) - vv.each do |v| - fact.public_send(:"#{sk}=", v) + add = + lambda do |target| + property_or_hash.each do |k, vv| + next unless target[k.to_s].nil? + (vv.is_a?(Array) ? vv : [vv]).each { |v| target.public_send(:"#{k}=", v) } + end + end + id = fact[fid]&.first + if id.nil? + add.call(fact) + else + fb.txn do |fbt| + add.call(fbt.query("(eq #{fid} #{id})").each.first || raise(Fbe::Error, "No facts by #{fid} = #{id}")) end end return diff --git a/test/fbe/test_overwrite.rb b/test/fbe/test_overwrite.rb index 61fbc1cd..3b56f93e 100644 --- a/test/fbe/test_overwrite.rb +++ b/test/fbe/test_overwrite.rb @@ -467,4 +467,51 @@ def test_returns_nil_when_something_changes f = fb.query('(always)').each.first assert_nil(Fbe.overwrite(f, { 'foo' => 'q' }, fb:)) end + + def test_adds_pair_of_properties_that_rules_accept_only_together + seed = Random.new_seed + repo = Random.new(seed).rand(1..1_000_000) + fbx = Fbe.fb(fb: Factbase.new, global: {}, options: Judges::Options.new, loog: Loog::NULL) + f = fbx.insert + f.what = 'foo-bar' + Fbe.overwrite(f, { repository: repo, where: 'github' }, fb: fbx) + assert_equal([repo], fbx.query('(always)').each.first['repository'], "pair is not added, seed #{seed}") + end + + def test_leaves_fact_untouched_when_added_properties_break_rules + seed = Random.new_seed + repo = Random.new(seed).rand(1..1_000_000) + fbx = Fbe.fb(fb: Factbase.new, global: {}, options: Judges::Options.new, loog: Loog::NULL) + f = fbx.insert + f.what = 'foo-bar' + begin + Fbe.overwrite(f, { repository: repo, where: 'bitbucket' }, fb: fbx) + rescue StandardError + nil + end + assert_nil(fbx.query('(always)').each.first['repository'], "half of a broken pair is kept, seed #{seed}") + end + + def test_adds_properties_in_place_to_fact_without_id + seed = Random.new_seed + count = Random.new(seed).rand(1..1_000_000) + fb = Factbase.new + f = fb.insert + f.what = 'ñame' + Fbe.overwrite(f, { count:, tag: 'ü' }, fb:) + assert_equal([count], fb.query('(always)').each.first['count'], "property is not added, seed #{seed}") + end + + def test_cannot_add_properties_to_fact_that_is_gone + seed = Random.new_seed + id = Random.new(seed).rand(1..1_000_000) + fb = Factbase.new + f = fb.insert + f._id = id + f.what = 'gõne' + fb.query("(eq _id #{id})").delete! + assert_raises(Fbe::Error, "a deleted fact got new properties, seed #{seed}") do + Fbe.overwrite(f, { tag: 'ü' }, fb:) + end + end end