phoenix_api_toolkit v0.2.0-alpha PhoenixApiToolkit.Ecto.DynamicFilters
Dynamic filtering of Ecto queries is useful for creating list/index functions, and ultimately list/index endpoints, that accept a map of filters to apply to the query. Such a map can be based on HTTP query parameters, naturally.
This module complements PhoenixApiToolkit.Ecto.GenericQueries
by leveraging the generic
queries provided by that module to filter a query dynamically based on a parameter map.
Several filtering types are so common that they have been implemented using standard filter macro's. This way, you only have to define which fields are filterable in what way.
Example without standard filters
def list_without_standard_filters(filters \\ %{}) do
from(user in "users", as: :user)
|> apply_filters(filters, fn
{:order_by, {field, direction}}, query ->
GenericQueries.order_by(query, :user, field, direction)
{literal, value}, query when literal in [:id, :name, :residence, :address] ->
GenericQueries.equals(query, :user, literal, value)
_, query ->
query
end)
end
# filtering is optional
iex> list_without_standard_filters()
#Ecto.Query<from u0 in "users", as: :user>
# multiple literal matches can be combined
iex> list_without_standard_filters(%{residence: "New York", address: "Main Street"})
#Ecto.Query<from u0 in "users", as: :user, where: u0.address == ^"Main Street", where: u0.residence == ^"New York">
# literal matches and sorting can be combined
iex> list_without_standard_filters(%{residence: "New York", order_by: {:name, :desc}})
#Ecto.Query<from u0 in "users", as: :user, where: u0.residence == ^"New York", order_by: [desc: u0.name]>
# other fields are ignored / passed through
iex> list_without_standard_filters(%{number_of_arms: 3})
#Ecto.Query<from u0 in "users", as: :user>
Example with standard filters with module attributes
The easiest to use and recommended form of standard filtering is the standard_filters/2
macro,
which reads several module attributes from the module in which it is used to provide its functionality.
@main_binding :user
@literals ~w(id username residence address)a
@sets ~w(roles)a
@smaller_than_map %{
inserted_before: :inserted_at,
updated_before: :updated_at
}
@smaller_than Map.keys(@smaller_than_map)
@greater_than_or_equals_map %{
inserted_at_or_after: :inserted_at,
updated_at_or_after: :updated_at
}
@greater_than_or_equals Map.keys(@greater_than_or_equals_map)
def by_username_prefix(query, prefix) do
from(user in query, where: ilike(user.username, ^"#{prefix}%"))
end
def list_with_standard_filters_and_attributes(filters \\ %{}) do
from(user in "users", as: :user)
|> apply_filters(filters, fn
# Add custom filters first and fallback to standard filters
{:username_prefix, value}, query -> by_username_prefix(query, value)
filter, query -> standard_filters(query, filter)
end)
end
# filtering is optional
iex> list_with_standard_filters_and_attributes()
#Ecto.Query<from u0 in "users", as: :user>
# filtering works the same way
iex> list_with_standard_filters_and_attributes(%{username: "Peter", inserted_before: DateTime.from_unix!(155555555)})
#Ecto.Query<from u0 in "users", as: :user, where: u0.inserted_at < ^~U[1974-12-06 09:52:35Z], where: u0.username == ^"Peter">
# limit, offset, and order_by are supported
iex> list_with_standard_filters_and_attributes(%{limit: 10, offset: 1, order_by: {:username, :desc}})
#Ecto.Query<from u0 in "users", as: :user, order_by: [desc: u0.username], limit: ^10, offset: ^1>
# complex custom filters can be user too
iex> list_with_standard_filters_and_attributes(%{username_prefix: "Pete"})
#Ecto.Query<from u0 in "users", as: :user, where: ilike(u0.username, ^"Pete%")>
# other fields are ignored / passed through
iex> list_with_standard_filters_and_attributes(%{number_of_arms: 3, order_by: {:boom, :asc}})
#Ecto.Query<from u0 in "users", as: :user>
Example with standard filters
It is possible to use the standard filters macro without using module attributes, by specifying (some of) the macro parameters directly.
def list_with_standard_filters(filters \\ %{}) do
from(user in "users", as: :user)
|> apply_filters(filters, fn
filter, query ->
standard_filters(
query,
filter,
:user,
[:username],
[:roles],
@smaller_than_map,
@smaller_than,
@greater_than_or_equals_map,
@greater_than_or_equals
)
end)
end
# filtering is optional
iex> list_with_standard_filters()
#Ecto.Query<from u0 in "users", as: :user>
# filtering works the same way
iex> list_with_standard_filters(%{username: "Peter"})
#Ecto.Query<from u0 in "users", as: :user, where: u0.username == ^"Peter">
Link to this section Summary
Functions
Applies filters
to query
by reducing filters
using filter_reductor
.
Combine with the generic queries from PhoenixApiToolkit.Ecto.GenericQueries
to write complex
filterables. Several standard filters have been implemented in
standard_filters/2
and standard_filters/9
.
Applies standard filters to the query. See apply_filters/3
for an example. Standard
filters include filters for literal matches, datetime relatives, set membership,
ordering and pagination.
Applies standard filters to the query. See apply_filters/3
for an example. Standard
filters include filters for literal matches, datetime relatives, set membership,
ordering and pagination.
Link to this section Types
Link to this section Functions
apply_filters(query, filters, filter_reductor)
Applies filters
to query
by reducing filters
using filter_reductor
.
Combine with the generic queries from PhoenixApiToolkit.Ecto.GenericQueries
to write complex
filterables. Several standard filters have been implemented in
standard_filters/2
and standard_filters/9
.
See the module docs Elixir.PhoenixApiToolkit.Ecto.DynamicFilters
for details and examples.
Applies standard filters to the query. See apply_filters/3
for an example. Standard
filters include filters for literal matches, datetime relatives, set membership,
ordering and pagination.
See the module docs Elixir.PhoenixApiToolkit.Ecto.DynamicFilters
for details and examples.
This macro requires that the following module attributes have been set:
@main_binding
: the named binding of the Ecto model that generic queries are applied to@literals
: fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.equals/4
@sets
: fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.member_of/4
@smaller_than_map
: map of virtual "smallerthan" fields and the actual fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.smaller_than/4
@smaller_than
: keys of@smaller_than_map
@greater_than_or_equals_map
: map of virtual "greaterthan_or_equals" fields and the actual fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.greater_than_or_equals/4
@greater_than_or_equals
: keys of@greater_than_or_equals_map
If these module attributes cannot be used, please use the fully parameterized version of this
macro, standard_filters/9
.
standard_filters(query, filter, main_binding, literals, sets, smaller_than_map, smaller_than, greater_than_or_equals_map, greater_than_or_equals)
(macro)Applies standard filters to the query. See apply_filters/3
for an example. Standard
filters include filters for literal matches, datetime relatives, set membership,
ordering and pagination.
See the module docs Elixir.PhoenixApiToolkit.Ecto.DynamicFilters
for details and examples.
This macro requires the following parameters:
main_binding
: the named binding of the Ecto model that generic queries are applied toliterals
: fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.equals/4
sets
: fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.member_of/4
smaller_than_map
: map of virtual "smallerthan" fields and the actual fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.smaller_than/4
smaller_than
: keys ofsmaller_than_map
greater_than_or_equals_map
: map of virtual "greaterthan_or_equals" fields and the actual fields comparable byPhoenixApiToolkit.Ecto.GenericQueries.greater_than_or_equals/4
greater_than_or_equals
: keys ofgreater_than_or_equals_map