ExSift 0.2.0 - Filter any Elixir type with MongoDB-style queries

ExSift 0.2.0 - Filter any Elixir type with MongoDB-style queries

ExSift 0.2.0 is out. The big headline: you can now filter anything not just plain maps. Keyword lists, MapSets, structs, and any custom type all work out of the box.


What’s new

Filter keyword lists

data = [[name: "Alice", age: 30], [name: "Bob", age: 25]]

ExSift.filter(data, %{"age" => %{"$gt" => 28}})
# => [[name: "Alice", age: 30]]

Filter structs directly

Shape matching, dot notation, and all operators work on any struct - no boilerplate needed:

defmodule User, do: defstruct [:name, :age, :role]

data = [
  %User{name: "Alice", age: 30, role: "admin"},
  %User{name: "Bob", age: 25, role: "user"},
  %User{name: "Charlie", age: 35, role: "admin"}
]

ExSift.filter(data, %{role: "admin", age: %{"$gte" => 30}})
# => [%User{name: "Alice", ...}, %User{name: "Charlie", ...}]

# Dot notation through nested structs
ExSift.filter(records, %{"address.city" => "NYC"})

MapSet fields - $size, $all, $in, $elemMatch

data = [
  %{user: "alice", perms: MapSet.new([:read, :write, :delete])},
  %{user: "bob",   perms: MapSet.new([:read])}
]

# Who has both :read and :write?
ExSift.filter(data, %{perms: %{"$all" => [:read, :write]}})
# => [%{user: "alice", ...}]

# Who has exactly 1 permission?
ExSift.filter(data, %{perms: %{"$size" => 1}})
# => [%{user: "bob", ...}]

# Who has :write in their perms?
ExSift.filter(data, %{perms: %{"$in" => [:write]}})
# => [%{user: "alice", ...}]

Custom types via the Collection protocol

Implement ExSift.Collection for any type and it will work with all operators:

defimpl ExSift.Collection, for: MyQueue do
  def fetch(q, key), do: Map.get(q, key)
  def to_pairs(q), do: [size: q.size, name: q.name]
  def member?(q, value), do: value in q.items
  def size(q), do: length(q.items)
end

# Now ExSift works on MyQueue natively
ExSift.filter(queues, %{name: "priority", "$size" => 5})

Compiled filters work across all types

compile/1 returns a plain function. Reuse it across any collection type:

is_senior = ExSift.compile(%{age: %{"$gte" => 30}})

Enum.filter(maps, is_senior)
Enum.filter(structs, is_senior)
Enum.filter(keyword_lists, is_senior)
Enum.count(all_users, is_senior)

Upgrading

No breaking changes. Drop in the new version:

{:ex_sift, "~> 0.2.0"}

Links

As always, feedback and PRs welcome!

1 Like