ex_sieve is a library offering a filtering solution for Ecto. It builds Ecto.Query structs from a ransack inspired query language.
It was originally developed by @valyukov, a few months ago I joined the project and started extending his great work. I have just released version 0.8.0, it includes a lot of new features and improvements, you can take a look at the changelog and at the documentation.
The main use case is to create a filter query on a list view taking as input user supplied URL query parameters. But this is not the only possible use, the Repo.filter function provided by the library takes as input a map that can be built in many different ways.
I’m wondering why you didn’t implement Ecto.Queryable for a struct holding the filter input. then this would be usable with all Repo functions and not just your custom injected filter/2. Misunderstood Repo.filter, which does return a query.
Yes, maybe the API it’s not the better one but I didn’t consider changing the original one before releasing 1.0.0. Your suggestion it’s indeed good, I will investigate on it, thanks!
Also for release 1.0 I would like to remove the remaining usage of Ecto internal APIs. In particular I didn’t find a way of building dynamic joins without relying on Ecto.Query.Builder.Join.join/10.
I admit I am not the bigger expert on macros but I tried with no success. The main problem is that all parameters needed for building the join are known only at runtime.
Not sure if this code is best, but at least it does not depend on private API:
defp do_apply_join({parent, relation} = parent_relation, query)
when is_atom(relation) and (is_binary(parent) or is_nil(parent)) and
(is_atom(query) or :erlang.map_get(:__struct__, query) == Ecto.Query) do
as = join_as(parent_relation)
query |> Macro.escape() |> join_build(parent_relation, as) |> elem(0)
end
defp join_build(query, {nil, relation}, as) do
Code.eval_quoted(
quote do
join(unquote(query), :inner, [p], a in assoc(p, unquote(relation)), as: unquote(as))
end
)
end
defp join_build(query, {string, relation}, as) do
parent = String.to_atom(string)
Code.eval_quoted(
quote do
join(unquote(query), :inner, [{unquote(parent), p}], a in assoc(p, unquote(relation)), as: unquote(as))
end
)
end
Notes:
Since we are using eval here I have added some guards. To make sure nothing would explode you may add extra runtime checks (regex?) for parent and relation contents.
Make sure to add import Ecto.Query and remove unused alias Ecto.Query.Builder.Join and join_binding/1 private function.
Ex_sieve seems very powerful, yet there’s also a simpler library - Flop - also in early dev stage. Kudos to authors and contributors!
Can someone well-versed in Ecto comment about the benefits and downsides in regard to how both libraries approach DB querying, eg. security, performance, query-composing and general dev experience?