magni-
magni-

Reputation: 2055

Matching a struct by a subset of its fields passed into the current function

I'm building a filtering API. Simplifying things, I have something that ressembles a function that takes in a list of structs and a map of filters. My issue is that pairing Enum.filter with &match? is not working when passing in the function parameter:

def filter_structs(structs, filters) do
  # this always returns []
  structs |> Enum.filter(&match?(^filters, &1))
end

But if instead I hardcode the filters, things work fine:

def filter_structs(structs, _filters) do
  structs |> Enum.filter(&match?(%{some_field: true}, &1))
end

I have this workaround working, but it's not very pretty... Is there no better solution?

def filter_structs(structs, filters) do
  structs
  |> Enum.filter(fn s -> 
    Map.equal?(filters, s |> Map.take(filters |> Map.keys))
  end)
end

Upvotes: 2

Views: 639

Answers (2)

Adam Millerchip
Adam Millerchip

Reputation: 23091

+1 to sabiwara's answer.

Your version is fine too, I think. Although I'd probably write it without pipes:

def filter_structs(structs, filters) do
  keys = Map.keys(filters)
  Enum.filter(structs, &(filters === Map.take(&1, keys)))
end

Upvotes: 2

sabiwara
sabiwara

Reputation: 3149

Maps can be pattern matched on a variable key using %{^key => _} and on a variable key, value pair using %{^key => ^value}.

Matching against the whole map using match?(^filters, map) will only return true if map === filters, not contain it.

The following implementation leverages pattern matching and is probably clearer about the intent:

  def filter_structs(structs, filters) do
    Enum.filter(structs, fn struct ->
      Enum.all?(filters, fn {field, value} ->
        match?(%{^field => ^value}, struct)
      end)
    end)
  end

Upvotes: 4

Related Questions