query_elf v0.2.0 QueryElf behaviour View Source
Defines an Ecto query builder.
Here's a simple usage example:
defmodule MyQueryBuilder do
use QueryElf,
schema: MySchema,
searchable_fields: [:id, :name]
def filter(:my_extra_filter, value, _query) do
dynamic([s], s.some_field - ^value == 0)
end
end
MyQueryBuilder.build_query(id__in: [1, 2, 3], my_extra_filter: 10)
It accepts the following options:
:schema
- theEcto.Schema
for which the queries will be built (required):searchable_fields
- a list of fields to build default filters for (default:[]
):sortable_fields
- a list of fields to build default sorters for (default:[]
):plugins
- a list of plugins that can be used to increment the query builder's functionality. (default:[]
)
If used, the :searchable_fields
option will define the following filters (according to the field
type in the schema):
id
orbinary_id
::$FIELD
- checks if the field is equal to the given value:$FIELD__neq
- checks if the field is different from the given value:$FIELD__in
- checks if the field is contained in the given enumerable
boolean
::$FIELD
- checks if the field is equal to the given value
integer
,float
ordecimal
::$FIELD
- checks if the field is equal to the given value:$FIELD__neq
- checks if the field is different from the given value:$FIELD__in
- checks if the field is contained in the given enumerable:$FIELD__gt
- checks if the field is greater than the given value:$FIELD__lt
- checks if the field is lower than the given value:$FIELD__gte
- checks if the field is greater than or equal to the given value:$FIELD__lte
- checks if the field is lower than or equal to the given value
string
::$FIELD
- checks if the field is equal to the given value:$FIELD__neq
- checks if the field is different from the given value:$FIELD__in
- checks if the field is contained in the given enumerable:$FIELD__contains
- checks if the field contains the given string:$FIELD__starts_with
- checks if the field starts with the given string:$FIELD__ends_with
- checks if the field ends with the given string
date
,time
,naive_datetime
,datetime
,time_usec
,naive_datetime_usec
ordatetime_usec
::$FIELD
- checks if the field is equal to the given value:$FIELD__neq
- checks if the field is different from the given value:$FIELD__in
- checks if the field is contained in the given enumerable:$FIELD__after
- checks if the field occurs after the given value:$FIELD__before
- checks if the field occurs before the given value
Link to this section Summary
Functions
Similar to Ecto.Query.join/{4,5}
, but can be called multiple times with the same alias.
Callbacks
Should return the query builder metadata requested.
Should return the base query in this query builder. This base query will be used when defining the
empty_query/0
and build_query/1
callbacks. If not defined, defaults to the supplied schema.
Should receive a a keyword list or a map containing parameters and use it to build a query.
Same thing as build_query/1
, but also receives some options for things like pagination and
ordering.
The same thing as build_query/2
, but instead of building a new query it receives and extends
an existing one.
Should return a query that when applied, returns nothing.
Receives an atom representing a filter, the parameter for the aforementioned filter and an Ecto query. Should return an Ecto dynamic or a tuple containing an Ecto query and an Ecto dynamic.
Receives an atom representing a field to order by, the order direction, an extra argument to perform the ordering, and an Ecto query. Should return the Ecto query with the appropriate sorting options applied.
Link to this section Types
Link to this section Functions
Similar to Ecto.Query.join/{4,5}
, but can be called multiple times with the same alias.
Note that only the first join operation is performed, the subsequent ones that use the same alias are just ignored. Also note that because of this behaviour, its mandatory to specify an alias when using this function.
This is helpful when you need to perform a join while building queries one filter at a time, because the same filter could be used multiple times or you could have multiple filters that require the same join.
This scenario poses a problem with how the filter/3
callback work, as you need to return a
dynamic with the filtering, which means that the join must have an alias, and by default Ecto
raises an error when you add multiple joins with the same alias.
To solve this, it is recommended to use this macro instead of the default Ecto.Query.join/{4,5}
.
As an added bonus, there will be only one join in the query that can be reused by multiple
filters.
Link to this section Callbacks
__query_builder__(metadata_type)
View Source__query_builder__(metadata_type()) :: term()
Should return the query builder metadata requested.
Should return the base query in this query builder. This base query will be used when defining the
empty_query/0
and build_query/1
callbacks. If not defined, defaults to the supplied schema.
This is useful when dealing with logical deletion or other business rules that need to be followed every time the query builder is used. Example:
defmodule UsersQueryBuilder do
use QueryElf,
schema: User,
searchable_fields: [:id, :name]
def base_query do
from u in User, where: is_nil(u.deleted_at)
end
end
Should receive a a keyword list or a map containing parameters and use it to build a query.
build_query(filter, options)
View Sourcebuild_query(filter(), options()) :: Ecto.Query.t()
Same thing as build_query/1
, but also receives some options for things like pagination and
ordering.
build_query(arg1, filter, options)
View Sourcebuild_query(Ecto.Query.t(), filter(), options()) :: Ecto.Query.t()
The same thing as build_query/2
, but instead of building a new query it receives and extends
an existing one.
Should return a query that when applied, returns nothing.
filter(atom, term, arg3)
View Sourcefilter(atom(), term(), Ecto.Query.t()) :: Ecto.Query.dynamic() | {Ecto.Query.t(), Ecto.Query.dynamic()}
Receives an atom representing a filter, the parameter for the aforementioned filter and an Ecto query. Should return an Ecto dynamic or a tuple containing an Ecto query and an Ecto dynamic.
Most of the filtering should happen in the returned dynamic, and returning a query should only be used when the query needs to be extended for the dynamic to make sense. Example:
# this is a simple comparison, so just returning a dynamic will suffice
def filter(:my_filter, value, _query) do
dynamic([s], s.some_field == ^value)
end
# this relies on a join, so the query must be extended accordingly
def filter(:my_other_filter, value, query) do
{
join(query, :left, [s], assoc(s, :some_relationship), as: :related),
dynamic([related: r], r.some_field == ^value)
}
end
sort(atom, sort_direction, term, arg4)
View Source (optional)sort(atom(), sort_direction(), term(), Ecto.Query.t()) :: Ecto.Query.t()
Receives an atom representing a field to order by, the order direction, an extra argument to perform the ordering, and an Ecto query. Should return the Ecto query with the appropriate sorting options applied.
Example:
def sort(:name, direction, _arg, query) do
sort(query, [s], [{^direction, s.name}])
end