improvement: improve the behavior of defaults

functional defaults are added at action time, others are added directly
into the changeset
This commit is contained in:
Zach Daniel 2021-03-17 22:34:49 -04:00
parent d4b27d5d4e
commit f3c85da268
5 changed files with 28 additions and 33 deletions

View file

@ -76,7 +76,7 @@ defmodule Ash.Actions.Create do
else else
Ash.Changeset.for_create(changeset, action.name, %{}, actor: actor) Ash.Changeset.for_create(changeset, action.name, %{}, actor: actor)
end end
|> Ash.Changeset.set_defaults(:create) |> Ash.Changeset.set_defaults(:create, true)
|> Ash.Changeset.cast_arguments(action) |> Ash.Changeset.cast_arguments(action)
end end

View file

@ -74,7 +74,7 @@ defmodule Ash.Actions.Update do
else else
Ash.Changeset.for_update(changeset, action.name, %{}, actor: actor) Ash.Changeset.for_update(changeset, action.name, %{}, actor: actor)
end end
|> Ash.Changeset.set_defaults(:update) |> Ash.Changeset.set_defaults(:update, true)
|> Ash.Changeset.cast_arguments(action) |> Ash.Changeset.cast_arguments(action)
end end

View file

@ -280,11 +280,6 @@ defmodule Ash.Changeset do
tenant: [ tenant: [
type: :any, type: :any,
doc: "set the tenant on the changeset" doc: "set the tenant on the changeset"
],
defaults: [
type: :any,
doc:
"A list of attributes and arguments to apply defaults for. Defaults to: []. Any unset defaults are set when the action is called."
] ]
] ]
@ -450,7 +445,7 @@ defmodule Ash.Changeset do
|> validate_relationships_accepted(action) |> validate_relationships_accepted(action)
|> cast_arguments(action, opts[:defaults], true) |> cast_arguments(action, opts[:defaults], true)
|> run_action_changes(action, opts[:actor]) |> run_action_changes(action, opts[:actor])
|> set_defaults(changeset.action_type, opts[:defaults] || []) |> set_defaults(changeset.action_type, false)
|> add_validations() |> add_validations()
|> require_values(changeset.action_type) |> require_values(changeset.action_type)
|> mark_validated(action.name) |> mark_validated(action.name)
@ -585,13 +580,16 @@ defmodule Ash.Changeset do
end end
@doc false @doc false
def set_defaults(changeset, action_type, keys \\ :all) def set_defaults(changeset, action_type, lazy? \\ false)
def set_defaults(changeset, :create, keys) do def set_defaults(changeset, :create, lazy?) do
changeset.resource changeset.resource
|> Ash.Resource.Info.attributes() |> Ash.Resource.Info.attributes()
|> Enum.filter(&(not is_nil(&1.default))) |> Enum.filter(&(not is_nil(&1.default)))
|> Enum.filter(&(keys == :all || &1.name in keys)) |> Enum.filter(fn attribute ->
lazy? or
not (is_function(attribute.default) or match?({_, _, _}, attribute.default))
end)
|> Enum.reduce(changeset, fn attribute, changeset -> |> Enum.reduce(changeset, fn attribute, changeset ->
force_change_new_attribute_lazy(changeset, attribute.name, fn -> force_change_new_attribute_lazy(changeset, attribute.name, fn ->
default(:create, attribute) default(:create, attribute)
@ -599,11 +597,14 @@ defmodule Ash.Changeset do
end) end)
end end
def set_defaults(changeset, :update, keys) do def set_defaults(changeset, :update, lazy?) do
changeset.resource changeset.resource
|> Ash.Resource.Info.attributes() |> Ash.Resource.Info.attributes()
|> Enum.filter(&(not is_nil(&1.update_default))) |> Enum.filter(&(not is_nil(&1.update_default)))
|> Enum.filter(&(keys == :all || &1.name in keys)) |> Enum.filter(fn attribute ->
lazy? or
not (is_function(attribute.update_default) or match?({_, _, _}, attribute.update_default))
end)
|> Enum.reduce(changeset, fn attribute, changeset -> |> Enum.reduce(changeset, fn attribute, changeset ->
force_change_new_attribute_lazy(changeset, attribute.name, fn -> force_change_new_attribute_lazy(changeset, attribute.name, fn ->
default(:update, attribute) default(:update, attribute)

View file

@ -217,11 +217,6 @@ defmodule Ash.Query do
tenant: [ tenant: [
type: :any, type: :any,
doc: "set the tenant on the query" doc: "set the tenant on the query"
],
defaults: [
type: :any,
doc:
"A list of arguments to apply defaults for. Defaults to: []. Any unset defaults are set when the action is called."
] ]
] ]
@ -257,7 +252,7 @@ defmodule Ash.Query do
|> cast_params(action, args) |> cast_params(action, args)
|> run_preparations(action, opts[:actor]) |> run_preparations(action, opts[:actor])
|> add_action_filters(action, opts[:actor]) |> add_action_filters(action, opts[:actor])
|> cast_arguments(action, opts[:defaults], true) |> cast_arguments(action, true)
else else
add_error(query, :action, "No such action #{action_name}") add_error(query, :action, "No such action #{action_name}")
end end
@ -801,23 +796,19 @@ defmodule Ash.Query do
end end
@doc false @doc false
def cast_arguments(query, action, defaults \\ :all, only_supplied? \\ false) do def cast_arguments(query, action, only_supplied? \\ false) do
action.arguments action.arguments
|> Enum.reject(& &1.private?) |> Enum.reject(& &1.private?)
|> Enum.reject(&(only_supplied? && match?({:ok, _}, fetch_argument(query, &1.name)))) |> Enum.reject(&(only_supplied? && match?({:ok, _}, fetch_argument(query, &1.name))))
|> Enum.reduce(query, fn argument, new_query -> |> Enum.reduce(query, fn argument, new_query ->
{ignore_nil_check, value} =
if defaults == :all || argument.name in (defaults || []) do
{false, get_argument(query, argument.name) || argument_default(argument.default)}
else
value = get_argument(query, argument.name) value = get_argument(query, argument.name)
{ignore_nil_check, value} =
if argument_default(argument.default) do if argument_default(argument.default) do
{true, value} {true, value}
else else
{false, value} {false, value}
end end
end
cond do cond do
!ignore_nil_check && is_nil(value) && !argument.allow_nil? -> !ignore_nil_check && is_nil(value) && !argument.allow_nil? ->

View file

@ -82,12 +82,12 @@ defmodule Ash.Resource.Attribute do
update_default: [ update_default: [
type: {:custom, Ash.OptionsHelpers, :default, []}, type: {:custom, Ash.OptionsHelpers, :default, []},
doc: doc:
"A zero argument function, an {mod, fun, args} triple or a value. If no value is provided for the attribute on update, this value is used." "A zero argument function, an {mod, fun, args} triple or a value. `Ash.Changeset.for_update/4` sets the default in the changeset if a value is not provided."
], ],
default: [ default: [
type: {:custom, Ash.OptionsHelpers, :default, []}, type: {:custom, Ash.OptionsHelpers, :default, []},
doc: doc:
"A zero argument function, an {mod, fun, args} triple or a value. If no value is provided for the attribute on create, this value is used." "A zero argument function, an {mod, fun, args} triple or a value. `Ash.Changeset.for_create/4` sets the default in the changeset if a value is not provided."
], ],
description: [ description: [
type: :string, type: :string,
@ -106,7 +106,10 @@ defmodule Ash.Resource.Attribute do
|> OptionsHelpers.set_default!(:writable?, false) |> OptionsHelpers.set_default!(:writable?, false)
|> OptionsHelpers.set_default!(:private?, true) |> OptionsHelpers.set_default!(:private?, true)
|> OptionsHelpers.set_default!(:default, &DateTime.utc_now/0) |> OptionsHelpers.set_default!(:default, &DateTime.utc_now/0)
|> OptionsHelpers.set_default!(:update_default, &DateTime.utc_now/0) |> OptionsHelpers.set_default!(
:update_default,
&DateTime.utc_now/0
)
|> OptionsHelpers.set_default!(:type, Ash.Type.UtcDatetimeUsec) |> OptionsHelpers.set_default!(:type, Ash.Type.UtcDatetimeUsec)
|> OptionsHelpers.set_default!(:allow_nil?, false) |> OptionsHelpers.set_default!(:allow_nil?, false)