Skip to content

Commit b8cfcc1

Browse files
authored
Merge pull request #5 from box-id/feature/add-array-remove-operator
Add array remove operator
2 parents 0ff049a + 28ef9e4 commit b8cfcc1

File tree

3 files changed

+167
-5
lines changed

3 files changed

+167
-5
lines changed

README.md

+6-5
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,9 @@ See the [JsonLogic.Extension](lib/json_logic/extension.ex) for how to implement
6767

6868
The following extensions are available, **but not enabled by default**:
6969

70-
| Operators | Extension Module |
71-
| -------------------------------- | --------------------------------- |
72-
| `obj` | `JsonLogic.Extensions.Obj` |
73-
| `replace` | `JsonLogic.Extensions.Replace` |
74-
| `encode_json`, `encode_json_obj` | `JsonLogic.Extensions.EncodeJson` |
70+
| Operators | Extension Module |
71+
| -------------------------------- | ---------------------------------- |
72+
| `obj` | `JsonLogic.Extensions.Obj` |
73+
| `replace` | `JsonLogic.Extensions.Replace` |
74+
| `array_remove` | `JsonLogic.Extensions.ArrayRemove` |
75+
| `encode_json`, `encode_json_obj` | `JsonLogic.Extensions.EncodeJson` |
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
defmodule JsonLogic.Extensions.ArrayRemove do
2+
@moduledoc """
3+
Extension that provides the `array_remove` operator which removes specified items from an array.
4+
5+
The operator accepts two arguments:
6+
- `array` (required): The source array to remove items from
7+
- `items` (required): Array of items to remove from the source array
8+
9+
If either argument is not an array, it will be converted to a single-item array.
10+
If the source array is nil, an empty array will be returned.
11+
12+
## Why use array_remove?
13+
While removing elements could theoretically be accomplished using array operations like "filter",
14+
the execution context within a "filter" prevents the use of dynamic variables. This makes it
15+
impossible to reference external variables for the items to be removed.
16+
17+
## Examples
18+
19+
iex> Logic.apply(%{
20+
...> "array_remove" => [
21+
...> [1, 2, 3, 4, 5],
22+
...> [2, 4]
23+
...> ]
24+
...> })
25+
[1, 3, 5]
26+
27+
iex> Logic.apply(%{
28+
...> "array_remove" => [
29+
...> %{"var" => "source"},
30+
...> %{"var" => "remove"}
31+
...> ]
32+
...> }, %{
33+
...> "source" => ["apple", "banana", "cherry"],
34+
...> "remove" => ["banana"]
35+
...> })
36+
["apple", "cherry"]
37+
"""
38+
@behaviour JsonLogic.Extension
39+
40+
@impl true
41+
def operations do
42+
%{
43+
"array_remove" => :operation_array_remove
44+
}
45+
end
46+
47+
@impl true
48+
def gen_code do
49+
quote do
50+
def operation_array_remove([source, items_to_remove], data) do
51+
source = __MODULE__.apply(source, data)
52+
items_to_remove = __MODULE__.apply(items_to_remove, data)
53+
54+
# Convert inputs to lists if they aren't already
55+
source_list = to_list(source)
56+
remove_list = to_list(items_to_remove)
57+
58+
# Remove all items in remove_list from source_list
59+
Enum.reject(source_list, &Enum.member?(remove_list, &1))
60+
end
61+
62+
defp to_list(nil), do: []
63+
defp to_list(x) when is_list(x), do: x
64+
defp to_list(x), do: [x]
65+
end
66+
end
67+
end

test/extensions/array_remove_test.exs

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
defmodule Extensions.ArrayRemoveTest do
2+
use ExUnit.Case, async: true
3+
4+
defmodule Logic do
5+
use JsonLogic.Base,
6+
extensions: [
7+
JsonLogic.Extensions.ArrayRemove
8+
]
9+
end
10+
11+
doctest JsonLogic.Extensions.ArrayRemove, import: Logic
12+
13+
describe "operation array_remove" do
14+
test "removes items from array with direct values" do
15+
assert [1, 3, 5] ==
16+
Logic.apply(%{
17+
"array_remove" => [
18+
[1, 2, 3, 4, 5],
19+
[2, 4]
20+
]
21+
})
22+
end
23+
24+
test "removes items from array using variables" do
25+
assert ["apple", "cherry"] ==
26+
Logic.apply(
27+
%{
28+
"array_remove" => [
29+
%{"var" => "fruits"},
30+
%{"var" => "remove"}
31+
]
32+
},
33+
%{
34+
"fruits" => ["apple", "banana", "cherry"],
35+
"remove" => ["banana"]
36+
}
37+
)
38+
end
39+
40+
test "handles non-array inputs" do
41+
assert [1, 3] ==
42+
Logic.apply(%{
43+
"array_remove" => [
44+
[1, 2, 3],
45+
2
46+
]
47+
})
48+
49+
assert [] ==
50+
Logic.apply(%{
51+
"array_remove" => [
52+
1,
53+
1
54+
]
55+
})
56+
end
57+
58+
test "handles nil inputs" do
59+
assert [] ==
60+
Logic.apply(%{
61+
"array_remove" => [
62+
nil,
63+
[1, 2]
64+
]
65+
})
66+
67+
assert [1, 2, 3] ==
68+
Logic.apply(%{
69+
"array_remove" => [
70+
[1, 2, 3],
71+
nil
72+
]
73+
})
74+
end
75+
76+
test "handles empty arrays" do
77+
assert [] ==
78+
Logic.apply(%{
79+
"array_remove" => [
80+
[],
81+
[1, 2]
82+
]
83+
})
84+
85+
assert [1, 2, 3] ==
86+
Logic.apply(%{
87+
"array_remove" => [
88+
[1, 2, 3],
89+
[]
90+
]
91+
})
92+
end
93+
end
94+
end

0 commit comments

Comments
 (0)