A simple query builder helper for Ash.Query
If available in Hex, the package can be installed
by adding ash_query_builder
to your list of dependencies in mix.exs
:
def deps do
[
{:ash_query_builder, "~> 0.7.1"}
]
end
AshQueryBuilder
is a helper to make it easy to serialize/deserialize URL queries into a structure that can be used to generate a Ash.Query
with filters and sorting. This is mainly useful when you need to create tables that can be sorted or filtered.
Example:
alias Plug.Conn.Query
# We first create our builder struct
builder = AshQueryBuilder.new()
# Now we can add multiple types of filters to it.
{builder, filter_1} = AshQueryBuilder.add_filter(builder, :updated_at, :<, DateTime.utc_now(), id: "my_custom_id")
{builder, filter_2} = AshQueryBuilder.add_filter(builder, :first_name, "in", ["blibs", "blobs"], [])
{builder, _} = AshQueryBuilder.add_filter(builder, [:organization], :name, :ilike, "MyOrg", enabled?: false)
{builder, _} = AshQueryBuilder.add_filter(builder, :created_by, :is_nil, nil, [])
{builder, _} = AshQueryBuilder.add_filter(builder, :surname, :left_word_similarity, "blobs", [])
# We can also add sorting rules
{builder, sorter_1} = AshQueryBuilder.add_sorter(builder, :updated_at, :desc)
{builder, sorter_2} = AshQueryBuilder.add_sorter(builder, :first_name, :asc)
# This will generate a map that can be stored into a URL query parameters
query_params = AshQueryBuilder.to_params(builder, with_disabled?: true)
# This will generate the URL query parameters, it is similar to just calling ~p"my_url?#{query_params}"
url_query_params = Query.encode(query_params)
# Now we can decode the query back and parse it into a new builder
builder = url_query_params |> Query.decode |> AshQueryBuilder.parse()
# Finally we can use the builder to create the actual Ash.Query
query = Ash.Query.new(Example.MyApi.User)
query = AshQueryBuilder.to_query(builder, query)
# And run the query
Example.MyApi.read!(query)
# We can also remove filters and sorters by id
builder = AshQueryBuilder.remove_filter(builder, filter_1.id)
builder = AshQueryBuilder.remove_sorter(builder, sorter_1.id)
# And replace existing ones
{:error, :not_found} = AshQueryBuilder.replace_filter(builder, filter_1.id, :updated_at, :<, DateTime.utc_now(), [])
{:ok, builder} = AshQueryBuilder.replace_filter(builder, filter_2.id, :first_name, :in, ["blibs", "blubs"], [])
{:error, :not_found} = AshQueryBuilder.replace_sorter(builder, sorter_1.id, :updated_at, :asc, [])
{:ok, builder} = AshQueryBuilder.replace_sorter(builder, filter_2.id, :first_name, :desc, [])
AshQueryBuilder
comes already with a lot of filters commonly used in PostgreSQL (you can find all of them in the lib/ash_query_builder/filter
directory).
If you need some other specific filter that the library don't support out of the box, you can just easily create it. For example, let's say you are using postgis
and want to filter by radius, you can create a filter for it like this:
defmodule MyFilter do
@moduledoc false
use AshQueryBuilder.Filter, operator: :by_radius
@impl true
def new(id, path, field, value, opts) do
enabled? = Keyword.get(opts, :enabled?, true)
struct(__MODULE__, id: id, field: field, path: path, value: value, enabled?: enabled?)
end
end
defimpl AshQueryBuilder.Filter.Protocol, for: MyFilter do
use AshQueryBuilder.Filter.QueryHelpers
def to_filter(filter, query) do
{longitude, latitude, distance_in_meters} = filter.value
Ash.Query.filter(
query,
expr(
fragment(
"ST_DWithin(?, ST_POINT(?, ?)::geography, ?)",
^make_ref(filter),
^longitude,
^latitude,
^distance_in_meters
)
)
)
end
def operator(_), do: MyFilter.operator()
end
And then you can use it like this:
builder = AshQueryBuilder.add_filter(builder, :geometry, :by_radius, {-86.79, 36.17, 1000})