I would also like to know how others are handling this sort of thing. I prefer passing a struct for the pattern-matching assurances. However, sometimes I only have an ID and the extra query to turn that into a struct seems like an unecessary query.
As an example, suppose I want to filter accounts by property. There are actually four different ways I typically would want to query that resource:
- Accounts for a specific Property struct
- Accounts for a specific Property ID
- Accounts for multiple Property structs
- Accounts for multiple Property IDs
This could be done via four separate functions:
Or, this could be done with a single
filter_accounts_by_property function with multiple heads that accepts a schema, an ID, multiple schemas, or multiple IDs.
I like the explicitness of the separate function names. On the other hand, if it is happening a lot the simplicity of a single function that knows how to handle these four common argument structures might be nice. This is something that could easily be a reusable convention in multiple cases. Naively, there could be a common function that converts the argument to a list of IDs with something like…
def to_list_ids(args), do: args |> Enum.wrap() |> to_ids()
defp to_ids([first | _] = items) when is_integer(first), do: items
defp to_ids(items), do: Enum.map(& &1.id)
Then just use that anytime you want to support the four common queries. Example:
def filter_accounts_by_property(query, property_or_properties) do
from account in query, where: account.property_id in ^to_list_ids(property_or_properties)
Part of me thinks it is against the spirit of elixir in terms of explicitness and the other part thinks it’s a little bit madness to continually repeat this ceremony.