Packages

Compose Ecto query from client

Current section

Files

Jump to
querie lib filter.ex
Raw

lib/filter.ex

defmodule Querie.Filter do
import Ecto.Query
@doc """
Apply filter on multiple column
Example:
%{
id: 10,
not: %{
type: "work"
},
or: %{
team_id: 10,
and: %{
team_id: 11,
role: "manager"
}
}
}
# id = 10 and not (type = "work") and (team_id = 10 or (team_id = 11 and role = "manager" ))
"""
def apply(query, %{filter: filter, sort: sort}) do
query
|> __MODULE__.apply(filter)
|> sort(sort)
end
def apply(query, filters) when is_list(filters) or is_map(filters) do
# skip field starts with underscore
grouped_by_type =
filters
|> Enum.reject(fn
{_, {column, _}} -> String.starts_with?(to_string(column), "_")
_ -> false
end)
|> Enum.group_by(fn
{:ref, _} -> :ref
_ -> :filter
end)
d_query = filter(:and, grouped_by_type[:filter] || [])
query
|> where([q], ^d_query)
|> join_ref(grouped_by_type[:ref])
end
@doc """
Apply filter on single column
If filter value is list, filter row that match any value in the list
"""
def filter(:and, filters) when is_map(filters) or is_list(filters) do
Enum.reduce(filters, true, fn {key, val}, acc ->
ft = filter(key, val)
cond do
ft && acc == true -> dynamic([q], ^ft)
ft -> dynamic([q], ^acc and ^ft)
true -> acc
end
end)
end
def filter(:or, filters) when is_map(filters) or is_list(filters) do
Enum.reduce(filters, false, fn {key, val}, acc ->
ft = filter(key, val)
cond do
ft && acc == false -> dynamic([q], ^ft)
ft -> dynamic([q], ^acc or ^ft)
true -> acc
end
end)
end
def filter(:not, filters) when is_map(filters) or is_list(filters) do
d_query = filter(:and, filters)
dynamic([q], not (^d_query))
end
def filter(:in, {_column, nil}) do
nil
end
def filter(:in, {column, values}) do
dynamic([q], field(q, ^column) in ^values)
end
def filter(:is, {column, nil}) do
dynamic([q], is_nil(field(q, ^column)))
end
def filter(:is, {column, value}) do
dynamic([q], field(q, ^column) == ^value)
end
def filter(_op, {_column, nil}) do
nil
end
def filter(:gt, {column, value}) do
dynamic([q], field(q, ^column) > ^value)
end
def filter(:lt, {column, value}) do
dynamic([q], field(q, ^column) < ^value)
end
def filter(:ge, {column, value}) do
dynamic([q], field(q, ^column) >= ^value)
end
def filter(:le, {column, value}) do
dynamic([q], field(q, ^column) > ^value)
end
def filter(:ne, {column, value}) do
dynamic([q], field(q, ^column) != ^value)
end
def filter(:not, {column, value}) do
filter(:ne, {column, value})
end
def filter(:between, {column, [lower, upper]}) do
case [lower, upper] do
[nil, nil] -> nil
[nil, upper] -> dynamic([q], field(q, ^column) < ^upper)
[lower, nil] -> dynamic([q], field(q, ^column) > ^lower)
_ -> dynamic([q], field(q, ^column) > ^lower and field(q, ^column) < ^upper)
end
end
@doc """
between inclusive
"""
def filter(:ibetween, {column, [lower, upper]}) do
case [lower, upper] do
[nil, nil] -> nil
[nil, upper] -> dynamic([q], field(q, ^column) <= ^upper)
[lower, nil] -> dynamic([q], field(q, ^column) >= ^lower)
_ -> dynamic([q], field(q, ^column) >= ^lower and field(q, ^column) <= ^upper)
end
end
def filter(:ibetween, _), do: nil
def filter(:between, _), do: nil
def filter(:has, {column, value}) do
dynamic([q], ^value in field(q, ^column))
end
def filter(:contains, {column, value}) do
dynamic([q], like(field(q, ^column), ^"%#{value}%"))
end
def filter(:icontains, {column, value}) do
dynamic([q], ilike(field(q, ^column), ^"%#{value}%"))
end
def filter(column, values) when is_list(values) do
filter(:in, {column, values})
end
def filter(column, value) do
filter(:is, {column, value})
end
def sort(query, fields) do
if is_nil(fields) or Enum.empty?(fields) do
query
else
order =
fields
|> Enum.map(fn {column, direction} ->
{direction, column}
end)
order_by(query, ^order)
end
end
def join_ref(query, nil), do: query
def join_ref(query, refs) when is_list(refs) do
Enum.reduce(refs, query, fn {_, ref_filter}, query ->
join_ref(query, ref_filter)
end)
end
def join_ref(query, {column, {model, filter, opts}}) do
foreign_key = Keyword.get(opts, :foreign_key, :"#{column}_id")
references = Keyword.get(opts, :references, :id)
ref_query = __MODULE__.apply(model, filter)
join(query, :inner, [a], b in ^ref_query, on: field(a, ^foreign_key) == field(b, ^references))
end
def join_ref(query, _), do: query
end