defmodule QueryBuilder.Extension do
@moduledoc ~S"""
Use this module to create an extension module to `QueryBuilder` for app specific query utilities.
Use your query builder extension module wherever you would normally use `QueryBuilder`
Example:
```
defmodule MyApp.QueryBuilder do
use QueryBuilder.Extension
defmacro __using__(opts) do
quote do
require QueryBuilder
QueryBuilder.__using__(unquote(opts))
end
end
# Add app specific query functions
#---------------------------------
def where_initcap(query, field, value) do
text_equals_condition = fn field, value, get_binding_fun ->
{field, binding} = get_binding_fun.(field)
Ecto.Query.dynamic([{^binding, x}], fragment("initcap(?)", ^value) == field(x, ^field))
end
query
|> where(&text_equals_condition.(field, value, &1))
end
end
defmodule MyApp.Accounts.User do
use MyApp.QueryBuilder
schema "users" do
field :name, :string
field :active, :boolean
end
end
defmodule MyApp.Accounts do
alias MyApp.QueryBuilder, as: QB
def list_users(opts \\ []) do
# Query list can include custom query functions as well:
# [where_initcap: {:name, "john"}, where: {:active, true}]
MyApp.Accounts.User
|> QB.from_list(opts)
|> Repo.all()
end
end
```
"""
defmacro __using__(_opts) do
quote do
# Expose all QueryBuilder functions: QueryBuilder.__info__(:functions)
defdelegate left_join(query, assoc_fields, filters \\ [], or_filters \\ []),
to: QueryBuilder
defdelegate maybe_where(query, bool, filters), to: QueryBuilder
defdelegate maybe_where(query, condition, assoc_fields, filters, or_filters \\ []),
to: QueryBuilder
defdelegate maybe_order_by(query, bool, value), to: QueryBuilder
defdelegate maybe_order_by(query, condition, assoc_fields, value),
to: QueryBuilder
defdelegate new(ecto_query), to: QueryBuilder
defdelegate paginate(query, repo, opts \\ []), to: QueryBuilder
defdelegate order_by(query, value), to: QueryBuilder
defdelegate order_by(query, assoc_fields, value), to: QueryBuilder
defdelegate preload(query, assoc_fields), to: QueryBuilder
defdelegate where(query, filters), to: QueryBuilder
defdelegate where(query, assoc_fields, filters, or_filters \\ []), to: QueryBuilder
defdelegate offset(query, value), to: QueryBuilder
defdelegate limit(query, value), to: QueryBuilder
@doc ~S"""
Allows to pass a list of operations through a keyword list.
Example:
```
QueryBuilder.from_list(query, [
where: [name: "John", city: "Anytown"],
preload: [articles: :comments]
])
```
"""
def from_list(query, nil), do: query
def from_list(query, []), do: query
def from_list(query, [{operation, arguments} | tail]) do
arguments =
cond do
is_tuple(arguments) -> Tuple.to_list(arguments)
is_list(arguments) -> [arguments]
true -> List.wrap(arguments)
end
apply(__MODULE__, operation, [query | arguments])
|> from_list(tail)
end
end
end
end