improvement: if "" fails to cast, cast it as nil instead

improvement: ReadActionRequiresActor error
improvement: `ensure_selected` change
This commit is contained in:
Zach Daniel 2021-06-28 01:33:31 -04:00
parent ff0de75138
commit ad0af9831c
6 changed files with 77 additions and 4 deletions

View file

@ -211,6 +211,8 @@ defmodule Ash.Changeset do
Datalayers currently are not notified of the `select` for a changeset(unlike queries), and creates/updates select all fields when they are performed. 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. A select provided on a changeset simply sets the unselected fields to `nil` before returning the result.
Use `ensure_selected/2` if you simply wish to make sure a field has been selected, without deselecting any other fields.
""" """
def select(changeset, fields, opts \\ []) do def select(changeset, fields, opts \\ []) do
if opts[:replace?] do if opts[:replace?] do
@ -220,6 +222,27 @@ defmodule Ash.Changeset do
end end
end end
@doc """
Ensures that the given attributes are selected.
The first call to `select/2` will *limit* the fields to only the provided fields.
Use `ensure_selected/2` to say "select this field (or these fields) without deselecting anything else".
See `select/2` for more.
"""
def ensure_selected(changeset, fields) do
if changeset.select do
Ash.Changeset.select(changeset, List.wrap(fields))
else
to_select =
changeset.resource
|> Ash.Resource.Info.attributes()
|> Enum.map(& &1.name)
Ash.Changeset.select(changeset, to_select)
end
end
@doc """ @doc """
Ensure the the specified attributes are `nil` in the changeset results. Ensure the the specified attributes are `nil` in the changeset results.
""" """

View file

@ -0,0 +1,16 @@
defmodule Ash.Error.Query.ReadActionRequiresActor do
@moduledoc "Used when an actor is referenced in a filter template, but no actor exists"
use Ash.Error.Exception
def_ash_error([], class: :invalid)
defimpl Ash.ErrorKind do
def id(_), do: Ash.UUID.generate()
def code(_), do: "actor_required"
def message(_error) do
"actor is required"
end
end
end

View file

@ -63,6 +63,7 @@ defmodule Ash.Query do
InvalidLimit, InvalidLimit,
InvalidOffset, InvalidOffset,
NoReadAction, NoReadAction,
ReadActionRequiresActor,
Required Required
} }
@ -367,7 +368,7 @@ defmodule Ash.Query do
defp add_action_filters(query, action, actor) do defp add_action_filters(query, action, actor) do
if Ash.Filter.template_references_actor?(action.filter) and is_nil(actor) do if Ash.Filter.template_references_actor?(action.filter) and is_nil(actor) do
Ash.Query.add_error(query, "Read action requires actor") Ash.Query.add_error(query, ReadActionRequiresActor.exception([]))
else else
built_filter = built_filter =
Ash.Filter.build_filter_from_template( Ash.Filter.build_filter_from_template(
@ -597,6 +598,8 @@ defmodule Ash.Query do
if the source field is not selected on the query/provided data an error will be produced. If loading 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 a relationship with a query, an error is produced if the query does not select the destination field
of the relationship. of the relationship.
Use `ensure_selected/2` if you simply wish to make sure a field has been selected, without deselecting any other fields.
""" """
def select(query, fields, opts \\ []) do def select(query, fields, opts \\ []) do
query = to_query(query) query = to_query(query)
@ -608,6 +611,14 @@ defmodule Ash.Query do
end end
end end
@doc """
Ensures that the given attributes are selected.
The first call to `select/2` will *limit* the fields to only the provided fields.
Use `ensure_selected/2` to say "select this field (or these fields) without deselecting anything else".
See `select/2` for more.
"""
def ensure_selected(query, fields) do def ensure_selected(query, fields) do
if query.select do if query.select do
Ash.Query.select(query, List.wrap(fields)) Ash.Query.select(query, List.wrap(fields))

View file

@ -72,4 +72,11 @@ defmodule Ash.Resource.Change.Builtins do
def select(value) do def select(value) do
{Ash.Resource.Change.Select, target: value} {Ash.Resource.Change.Select, target: value}
end end
@doc """
Passes the provided value into `Ash.Changeset.ensure_selected/2`
"""
def ensure_selected(value) do
{Ash.Resource.Change.Select, target: value, ensure?: true}
end
end end

View file

@ -3,6 +3,10 @@ defmodule Ash.Resource.Change.Select do
use Ash.Resource.Change use Ash.Resource.Change
def change(changeset, opts, _) do def change(changeset, opts, _) do
if opts[:ensure?] do
Ash.Changeset.ensure_selected(changeset, opts[:target] || [])
else
Ash.Changeset.select(changeset, opts[:target] || []) Ash.Changeset.select(changeset, opts[:target] || [])
end end
end end
end

View file

@ -356,12 +356,24 @@ defmodule Ash.Type do
{:ok, value} {:ok, value}
:error -> :error ->
case term do
"" ->
cast_input(type, nil, constraints)
_ ->
{:error, "is invalid"} {:error, "is invalid"}
end
{:error, other} -> {:error, other} ->
case term do
"" ->
cast_input(type, nil, constraints)
_ ->
{:error, other} {:error, other}
end end
end end
end
@doc """ @doc """
Casts a value from the data store to an instance of the type, or errors Casts a value from the data store to an instance of the type, or errors