mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 13:03:02 +12:00
improvement: support the datalayer selecting fields in reads
This commit is contained in:
parent
05c933215c
commit
ec57f363ed
7 changed files with 80 additions and 3 deletions
|
@ -4,6 +4,7 @@ locals_without_parens = [
|
|||
accept: 1,
|
||||
action: 1,
|
||||
allow_nil?: 1,
|
||||
always_select?: 1,
|
||||
args: 1,
|
||||
argument: 2,
|
||||
argument: 3,
|
||||
|
|
|
@ -23,7 +23,7 @@ defmodule Ash.Actions.Helpers do
|
|||
resource
|
||||
|> Ash.Resource.Info.attributes()
|
||||
|> Enum.flat_map(fn attribute ->
|
||||
if attribute.private? || attribute.primary_key? || attribute.name in select do
|
||||
if attribute.always_select? || attribute.primary_key? || attribute.name in select do
|
||||
[]
|
||||
else
|
||||
[attribute.name]
|
||||
|
@ -34,4 +34,22 @@ defmodule Ash.Actions.Helpers do
|
|||
end)
|
||||
|> Ash.Resource.Info.put_metadata(:selected, select)
|
||||
end
|
||||
|
||||
def attributes_to_select(%{select: nil, resource: resource}) do
|
||||
resource
|
||||
|> Ash.Resource.Info.attributes()
|
||||
|> Enum.map(& &1.name)
|
||||
end
|
||||
|
||||
def attributes_to_select(%{select: select, resource: resource}) do
|
||||
resource
|
||||
|> Ash.Resource.Info.attributes()
|
||||
|> Enum.flat_map(fn attribute ->
|
||||
if attribute.always_select? || attribute.primary_key? || attribute.name in select do
|
||||
[]
|
||||
else
|
||||
[attribute.name]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -320,6 +320,12 @@ defmodule Ash.Actions.Read do
|
|||
{:ok, query} <- query,
|
||||
{:ok, filter} <-
|
||||
filter_with_related(relationship_filter_paths, ash_query, data),
|
||||
{:ok, query} <-
|
||||
Ash.DataLayer.select(
|
||||
query,
|
||||
Helpers.attributes_to_select(ash_query),
|
||||
ash_query.resource
|
||||
),
|
||||
{:ok, query} <-
|
||||
add_aggregates(
|
||||
query,
|
||||
|
@ -575,7 +581,7 @@ defmodule Ash.Actions.Read do
|
|||
} = data ->
|
||||
query =
|
||||
initial_query
|
||||
|> Ash.Query.unset([:filter, :aggregates, :sort, :limit, :offset])
|
||||
|> Ash.Query.unset([:filter, :aggregates, :sort, :limit, :offset, :select])
|
||||
|> Ash.Query.limit(initial_limit)
|
||||
|> Ash.Query.offset(initial_offset)
|
||||
|> Ash.Query.filter(^auth_filter)
|
||||
|
|
|
@ -207,6 +207,9 @@ defmodule Ash.Changeset do
|
|||
if the source field is not selected on the query/provided data an error will be produced. If loading
|
||||
a relationship with a query, an error is produced if the query does not select the destination field
|
||||
of the relationship.
|
||||
|
||||
Datalayers currently are not notified of the `select` for a changeset(unlike queries), and creates/updates select all fields when they are performed.
|
||||
A select provided on a changeset simply sets the unselected fields to `nil` before returning the result.
|
||||
"""
|
||||
def select(changeset, fields, opts \\ []) do
|
||||
if opts[:replace?] do
|
||||
|
|
|
@ -20,6 +20,7 @@ defmodule Ash.DataLayer do
|
|||
| {:join, Ash.Resource.t()}
|
||||
| {:aggregate, Ash.Query.Aggregate.kind()}
|
||||
| {:query_aggregate, Ash.Query.Aggregate.kind()}
|
||||
| :select
|
||||
| :aggregate_filter
|
||||
| :aggregate_sort
|
||||
| :boolean_filter
|
||||
|
@ -57,6 +58,11 @@ defmodule Ash.DataLayer do
|
|||
offset :: non_neg_integer(),
|
||||
resource :: Ash.Resource.t()
|
||||
) :: {:ok, data_layer_query()} | {:error, term}
|
||||
@callback select(
|
||||
data_layer_query(),
|
||||
select :: list(atom),
|
||||
resource :: Ash.Resource.t()
|
||||
) :: {:ok, data_layer_query()} | {:error, term}
|
||||
@callback set_tenant(Ash.Resource.t(), data_layer_query(), term) ::
|
||||
{:ok, data_layer_query()} | {:error, term}
|
||||
@callback resource_to_query(Ash.Resource.t(), Ash.Api.t()) :: data_layer_query()
|
||||
|
@ -121,6 +127,7 @@ defmodule Ash.DataLayer do
|
|||
destroy: 2,
|
||||
filter: 3,
|
||||
sort: 3,
|
||||
select: 3,
|
||||
limit: 3,
|
||||
offset: 3,
|
||||
transaction: 2,
|
||||
|
@ -302,6 +309,19 @@ defmodule Ash.DataLayer do
|
|||
end
|
||||
end
|
||||
|
||||
@spec select(data_layer_query(), offset :: list(atom), Ash.Resource.t()) ::
|
||||
{:ok, data_layer_query()} | {:error, term}
|
||||
def select(query, nil, _resource), do: {:ok, query}
|
||||
|
||||
def select(query, select, resource) do
|
||||
if can?(:select, resource) do
|
||||
data_layer = Ash.DataLayer.data_layer(resource)
|
||||
data_layer.select(query, select, resource)
|
||||
else
|
||||
{:ok, query}
|
||||
end
|
||||
end
|
||||
|
||||
@spec add_aggregate(data_layer_query(), Ash.Query.Aggregate.t(), Ash.Resource.t()) ::
|
||||
{:ok, data_layer_query()} | {:error, term}
|
||||
def add_aggregate(query, aggregate, resource) do
|
||||
|
|
|
@ -523,7 +523,7 @@ defmodule Ash.Query do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Ensure that only the specified attributes are present in the results.
|
||||
Ensure that only the specified *attributes* are present in the results.
|
||||
|
||||
The first call to `select/2` will replace the default behavior of selecting
|
||||
all attributes. Subsequent calls to `select/2` will combine the provided
|
||||
|
|
|
@ -9,6 +9,7 @@ defmodule Ash.Resource.Attribute do
|
|||
:primary_key?,
|
||||
:private?,
|
||||
:writable?,
|
||||
:always_select?,
|
||||
:default,
|
||||
:update_default,
|
||||
:description,
|
||||
|
@ -50,6 +51,34 @@ defmodule Ash.Resource.Attribute do
|
|||
doc:
|
||||
"Whether or not the attribute value contains sensitive information, like PII. If so, it will be redacted while inspecting data."
|
||||
],
|
||||
always_select?: [
|
||||
type: :boolean,
|
||||
default: false,
|
||||
doc: """
|
||||
Whether or not to always select this attribute when reading from the database.
|
||||
Useful if fields are used in read action preparations consistently.
|
||||
|
||||
A primary key attribute *cannot be deselected*, so this option will have no effect.
|
||||
|
||||
Generally, you should favor selecting the field that you need while running your preparation. For example:
|
||||
|
||||
```elixir
|
||||
defmodule MyApp.QueryPreparation.Thing do
|
||||
use Ash.Resource.Preparation
|
||||
|
||||
def prepare(query, _, _) do
|
||||
query
|
||||
|> Ash.Query.select(:attribute_i_need)
|
||||
|> Ash.Query.after_action(fn query, results ->
|
||||
{:ok, Enum.map(results, fn result ->
|
||||
do_something_with_attribute_i_need(result)
|
||||
end)}
|
||||
end)
|
||||
end
|
||||
end
|
||||
```
|
||||
"""
|
||||
],
|
||||
primary_key?: [
|
||||
type: :boolean,
|
||||
default: false,
|
||||
|
|
Loading…
Reference in a new issue