mirror of
https://github.com/ash-project/ash.git
synced 2024-09-20 05:23:03 +12:00
feat: add data layer custom filters
This commit is contained in:
parent
7820adeebe
commit
377319e881
4 changed files with 51 additions and 3 deletions
22
README.md
22
README.md
|
@ -90,6 +90,28 @@ Filters
|
|||
- lift `or` filters over the same field equaling a value into a single `in` filter, for performance (potentially)
|
||||
- make runtime filtering of a list of records + an ash filter a public interface. At first, we can just run a query with that filter, and filter for matches. Eventually it can be optimized.
|
||||
|
||||
This is from a conversation about how we might support on-demand calculated attributes
|
||||
that would allow doing something like getting the trigram similarity to a piece of text
|
||||
and filtering on it in the same request
|
||||
|
||||
```elixir
|
||||
:representatives
|
||||
|> Api.query()
|
||||
|> Ash.Query.filter(first_name: [trigram: [text: "Geoff", similarity: [greater_than: 0.1]]])
|
||||
|> Api.read!()
|
||||
|
||||
calculated_attributes do
|
||||
on_demand :trigram, [:first_name, :last_name]
|
||||
# calculated(:name_similarity, :float, calculate: [trigram_similarity: {:input, :text}])
|
||||
end
|
||||
|
||||
:representatives
|
||||
|> Api.query()
|
||||
|> Ash.Query.calculate(first_name_similarity: [first_name: [trigram: [text: "Geoff"]]])
|
||||
|> Ash.Query.filter(first_name_similarity: [greater_than: 0.1])
|
||||
|> Api.read!()
|
||||
```
|
||||
|
||||
Actions
|
||||
|
||||
- all actions need to be performed in a transaction
|
||||
|
|
|
@ -87,6 +87,11 @@ defmodule Ash do
|
|||
data_layer && data_layer.can?(resource, feature)
|
||||
end
|
||||
|
||||
@spec data_layer_filters(resource) :: map
|
||||
def data_layer_filters(resource) do
|
||||
Ash.DataLayer.custom_filters(resource)
|
||||
end
|
||||
|
||||
@spec resources(api) :: list(resource())
|
||||
def resources(api) do
|
||||
api.resources()
|
||||
|
|
|
@ -7,6 +7,7 @@ defmodule Ash.DataLayer do
|
|||
| {:filter_related, Ash.relationship_cardinality()}
|
||||
| :upsert
|
||||
|
||||
@callback custom_filters(Ash.resource()) :: map()
|
||||
@callback filter(Ash.data_layer_query(), Ash.filter(), resource :: Ash.resource()) ::
|
||||
{:ok, Ash.data_layer_query()} | {:error, Ash.error()}
|
||||
@callback sort(Ash.data_layer_query(), Ash.sort(), resource :: Ash.resource()) ::
|
||||
|
@ -34,6 +35,7 @@ defmodule Ash.DataLayer do
|
|||
|
||||
@optional_callbacks transaction: 2
|
||||
@optional_callbacks upsert: 2
|
||||
@optional_callbacks custom_filters: 1
|
||||
|
||||
@spec resource_to_query(Ash.resource()) :: Ash.data_layer_query()
|
||||
def resource_to_query(resource) do
|
||||
|
@ -112,4 +114,14 @@ defmodule Ash.DataLayer do
|
|||
{:ok, func.()}
|
||||
end
|
||||
end
|
||||
|
||||
def custom_filters(resource) do
|
||||
data_layer = Ash.data_layer(resource)
|
||||
|
||||
if :erlang.function_exported(data_layer, :custom_filters, 1) do
|
||||
data_layer.custom_filters(resource)
|
||||
else
|
||||
%{}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -868,10 +868,19 @@ defmodule Ash.Filter do
|
|||
defp parse_predicate(resource, predicate_name, attr_name, attr_type, value) do
|
||||
data_layer = Ash.data_layer(resource)
|
||||
|
||||
data_layer_predicates =
|
||||
Map.get(Ash.data_layer_filters(resource), Ash.Type.storage_type(attr_type), [])
|
||||
|
||||
all_predicates =
|
||||
Enum.reduce(data_layer_predicates, @predicates, fn {name, module}, all_predicates ->
|
||||
Map.put(all_predicates, name, module)
|
||||
end)
|
||||
|
||||
with {:predicate_type, {:ok, predicate_type}} <-
|
||||
{:predicate_type, Map.fetch(@predicates, predicate_name)},
|
||||
{:predicate_type, Map.fetch(all_predicates, predicate_name)},
|
||||
{:type_can?, _, true} <-
|
||||
{:type_can?, predicate_name,
|
||||
Keyword.has_key?(data_layer_predicates, predicate_name) or
|
||||
Ash.Type.supports_filter?(resource, attr_type, predicate_name, data_layer)},
|
||||
{:data_layer_can?, _, true} <-
|
||||
{:data_layer_can?, predicate_name,
|
||||
|
@ -881,7 +890,7 @@ defmodule Ash.Filter do
|
|||
{:ok, predicate}
|
||||
else
|
||||
{:predicate_type, :error} ->
|
||||
{:error, "No such filter type #{predicate_name}"}
|
||||
{:error, :predicate_type, "No such filter type #{predicate_name}"}
|
||||
|
||||
{:predicate, attr_name, {:error, error}} ->
|
||||
{:error, Map.put(error, :field, attr_name)}
|
||||
|
|
Loading…
Reference in a new issue