View Source AshQueryBuilder

A simple query builder helper for Ash.Query

installation

Installation

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.3.0"}
  ]
end

usage

Usage

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())
{builder, filter_2} = AshQueryBuilder.add_filter(builder, :first_name, "in", ["blibs", "blobs"])
{builder, _} = AshQueryBuilder.add_filter(builder, [:organization], :name, :ilike, "MyOrg")
{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)

# 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)

## Expanding

`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),

do: struct(__MODULE__, id: id, field: field, path: path, value: value)

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})