From 623577b35e24ea5a296cc6208eb4ba4346d4cc81 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Wed, 23 Oct 2019 17:31:21 +0200 Subject: [PATCH] Add RDF.Description.update/4 --- CHANGELOG.md | 2 ++ lib/rdf/description.ex | 48 +++++++++++++++++++++++++++++----- test/unit/description_test.exs | 36 +++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d235760..76aa2d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ This project adheres to [Semantic Versioning](http://semver.org/) and ### Added +- `RDF.Description.update/4` updates the objects of a predicate in a description + with a custom update function - `RDF.Description.take/2` creates a description from another one by limiting its statements to a set of predicates - `RDF.Graph.take/3` creates a graph from another one by limiting diff --git a/lib/rdf/description.ex b/lib/rdf/description.ex index 22a09bd..3454a43 100644 --- a/lib/rdf/description.ex +++ b/lib/rdf/description.ex @@ -367,13 +367,49 @@ defmodule RDF.Description do |> List.first end + @doc """ + Updates the objects of the `predicate` in `description` with the given function. -# def update(description = %RDF.Description{}, predicate, initial \\ [], fun) do -# triple_predicate = coerce_predicate(predicate) -# description.predicates -# |> Map.update(triple_predicate, initial, fn objects -> -# end) -# end + If `predicate` is present in `description` with `objects` as value, + `fun` is invoked with argument `objects` and its result is used as the new + list of objects of `predicate`. If `predicate` is not present in `description`, + `initial` is inserted as the `objects` of `predicate`. The initial value will + not be passed through the update function. + + The initial value and the returned objects by the update function will automatically + coerced to proper RDF object values before added. + + ## Examples + + iex> RDF.Description.new({EX.S, EX.p, EX.O}) |> + ...> RDF.Description.update(EX.p, fn objects -> [EX.O2 | objects] end) + RDF.Description.new([{EX.S, EX.p, EX.O}, {EX.S, EX.p, EX.O2}]) + iex> RDF.Description.new(EX.S) |> + ...> RDF.Description.update(EX.p, EX.O, fn _ -> EX.O2 end) + RDF.Description.new({EX.S, EX.p, EX.O}) + + """ + def update(description = %RDF.Description{}, predicate, initial \\ nil, fun) do + predicate = coerce_predicate(predicate) + + case get(description, predicate) do + nil -> + if initial do + put(description, predicate, initial) + else + description + end + + objects -> + objects + |> fun.() + |> List.wrap() + |> case do + [] -> delete_predicates(description, predicate) + objects -> put(description, predicate, objects) + end + end + end @doc """ diff --git a/test/unit/description_test.exs b/test/unit/description_test.exs index 02dc7f1..4fc7f28 100644 --- a/test/unit/description_test.exs +++ b/test/unit/description_test.exs @@ -317,6 +317,42 @@ defmodule RDF.DescriptionTest do end end + describe "update/4" do + test "list values returned from the update function become new coerced objects of the predicate" do + assert Description.new(EX.S, EX.P, [EX.O1, EX.O2]) + |> Description.update(EX.P, + fn [_object | other] -> [EX.O3 | other] end) == + Description.new(EX.S, EX.P, [EX.O3, EX.O2]) + end + + test "single values returned from the update function becomes new object of the predicate" do + assert Description.new(EX.S, EX.P, [EX.O1, EX.O2]) + |> Description.update(EX.P, fn _ -> EX.O3 end) == + Description.new(EX.S, EX.P, EX.O3) + end + + test "returning an empty list or nil from the update function causes a removal of the predications" do + description = EX.S + |> EX.p(EX.O1, EX.O2) + assert description + |> Description.update(EX.p, fn _ -> [] end) == + Description.new(EX.S, {EX.p, []}) + assert description + |> Description.update(EX.p, fn _ -> nil end) == + Description.new(EX.S, {EX.p, []}) + end + + test "when the property is not present the initial object value is added for the predicate and the update function not called" do + fun = fn _ -> raise "should not be called" end + assert Description.new(EX.S) + |> Description.update(EX.P, EX.O, fun) == + Description.new(EX.S, EX.P, EX.O) + + assert Description.new(EX.S) + |> Description.update(EX.P, fun) == + Description.new(EX.S) + end + end test "pop" do assert Description.pop(Description.new(EX.S)) == {nil, Description.new(EX.S)}