Current section
Files
Jump to
Current section
Files
lib/filterable.ex
defmodule Filterable do
@default_options [allow_blank: false, allow_nil: false, trim: true, default: nil, cast: nil]
defmacro __using__(_) do
quote do
import unquote(__MODULE__), only: [filterable: 2, filterable: 1]
@before_compile unquote(__MODULE__)
@filters_module __MODULE__
@filter_options []
end
end
defmacro __before_compile__(_) do
quote do
def apply_filters!(queryable, params, opts \\ []) do
Filterable.apply_filters!(queryable, params, @filters_module, filter_options(opts))
end
def apply_filters(queryable, params, opts \\ []) do
Filterable.apply_filters(queryable, params, @filters_module, filter_options(opts))
end
def filter_values(params, opts \\ []) do
Filterable.filter_values(params, @filters_module, filter_options(opts))
end
def filter_options(opts \\ []) do
Keyword.merge(opts, @filter_options)
end
defoverridable [apply_filters!: 3, apply_filters!: 2,
apply_filters: 3, apply_filters: 2,
filter_values: 2, filter_values: 1,
filter_options: 1, filter_options: 0]
end
end
defmacro filterable(arg, opts \\ [])
defmacro filterable([do: block], opts),
do: filterable(nil, block, opts)
defmacro filterable(arg, do: block),
do: filterable(nil, block, arg)
defmacro filterable(arg, opts),
do: filterable(arg, nil, opts)
def apply_filters!(queryable, params, module, opts \\ []) do
case apply_filters(queryable, params, module, opts) do
{:ok, result, values} -> {result, values}
{:error, message} -> raise Filterable.FilterError, message
end
end
def apply_filters(queryable, params, module, opts \\ []) do
with {:ok, values} <- filter_values(params, module, opts),
{:ok, result} <- module.defined_filters
|> Enum.reduce_while({:ok, queryable}, fn ({filter_name, filter_opts}, {:ok, queryable}) ->
options = Keyword.merge(opts, filter_opts)
value = Map.get(values, filter_name)
case apply_filter(queryable, module, filter_name, value, options) do
error = {:error, _} -> {:halt, error}
queryable -> {:cont, {:ok, queryable}}
end
end),
do: {:ok, result, values}
end
def filter_values(params, module, opts \\ []) do
Enum.reduce_while module.defined_filters, {:ok, %{}}, fn ({filter_name, filter_opts}, {:ok, acc}) ->
options =
[param: filter_name]
|> Keyword.merge(@default_options)
|> Keyword.merge(filter_opts)
|> Keyword.merge(opts)
case Filterable.Params.filter_value(params, options) do
{:ok, nil} -> {:cont, {:ok, acc}}
{:ok, val} -> {:cont, {:ok, Map.put(acc, filter_name, val)}}
error = {:error, _} -> {:halt, error}
end
end
end
defp apply_filter(queryable, module, filter_name, value, opts) do
share = Keyword.get(opts, :share)
allow_nil = Keyword.get(opts, :allow_nil)
try do
cond do
(allow_nil || value) && share ->
apply(module, filter_name, [queryable, value, share])
allow_nil || value ->
apply(module, filter_name, [queryable, value])
true -> queryable
end
rescue
FunctionClauseError -> queryable
end
end
defp filterable(module, block, opts) do
quote do
@filter_options unquote(opts)
@filters_module unquote(module) || Module.concat([__MODULE__, Filterable])
if unquote(is_tuple(block)) do
defmodule @filters_module do
use Filterable.DSL
unquote(block)
end
end
end
end
end