def list_food(opts \\ [])
# our defaults
opts = Enum.into(opts, %{limit: 5})
from(f in Food)
|> build_query(opts)
|> Repo.all()
end
# we expect the 2 arity function to always receive the option as a map.
# We then convert the map to a list of keys, and use it as the 3rd parameter
def build_query(query, opts), do: build_query(query, opts, Map.keys(opts))
# match for the :limit option
def build_query(q, %{limit: value}, [:limit | t]) do
limit(query, value)
|> build_query(opts, t)
end
# match for the :origin option
def build_query(q, %{origin: loc}, [:origin | t]) when is_binary(loc) do
where(query,[f], f.origin == ^loc)
|> build_query(opts, t)
end
# match for the :is_dry option
def build_query(q, %{is_dry: true}, [:is_dry | t]) do
where(query,[f], food.type == "dry")
|> build_query(opts, t)
end
# control the option execution stack as needed
# to process this option, we need to have a join with brands first
def build_query(q, %{country: iso}, [:is_dry | t]) when is_binary(iso) do
if has_named_binding?(q, :brands) do
# we utilize the named binding to filter by the country's ISO abbreviation
where(query,[f, brands: b], b.iso == ^iso)
|> build_query(opts, t)
else
# add the :brands key to the front of the stack, then add country again, then the remaining tail end.
build_query(q, opts, [:brands, :country] ++ t)
end
end
# we add this join on demand, as not every query needs it
def build_query(q, _opts, [:brands |t]) do
join(:left, [f], b in Brands, on: food.brand_id == b.id, as: :brands)
|> build_query(opts, t)
end
# this is for unrecognized options, we skip over it
def build_query(q, opts, [_ | t]), do: build_query(q, opts, t)
# no more options to process, let's exit the function now
def build_query(q, _, []), do: q