improvement: replace templates in change/preparation/validation opts

Previously, only a few specific changes supported using `arg/1` or `context/1`
in their options. Now, those templates can be used in any change options,
built-in or not.
This commit is contained in:
Zach Daniel 2022-10-31 02:25:01 -04:00
parent e18a8782c7
commit 113d2452d2
5 changed files with 77 additions and 46 deletions

View file

@ -520,7 +520,7 @@ defmodule Ash.Changeset do
opts[:tracer],
metadata
)
|> add_validations(opts[:tracer], metadata)
|> add_validations(opts[:tracer], metadata, opts[:actor])
|> mark_validated(action.name)
end
end
@ -650,7 +650,7 @@ defmodule Ash.Changeset do
opts[:tracer],
metadata
)
|> add_validations(opts[:tracer], metadata)
|> add_validations(opts[:tracer], metadata, opts[:actor])
|> mark_validated(action.name)
|> eager_validate_identities()
@ -942,6 +942,14 @@ defmodule Ash.Changeset do
} do
Ash.Tracer.set_metadata(opts[:tracer], :validation, metadata)
opts =
Ash.Filter.build_filter_from_template(
opts,
actor,
changeset.arguments,
changeset.context
)
module.validate(changeset, opts) == :ok
end
end
@ -955,6 +963,14 @@ defmodule Ash.Changeset do
Ash.Tracer.set_metadata(opts[:tracer], :change, metadata)
opts =
Ash.Filter.build_filter_from_template(
opts,
actor,
changeset.arguments,
changeset.context
)
module.change(changeset, opts, %{
actor: actor,
authorize?: authorize? || false,
@ -967,7 +983,7 @@ defmodule Ash.Changeset do
end
%{validation: _} = validation, changeset ->
validate(changeset, validation, tracer, metadata)
validate(changeset, validation, tracer, metadata, actor)
end)
end
@ -1113,34 +1129,42 @@ defmodule Ash.Changeset do
defp default(:update, %{update_default: value}), do: value
defp add_validations(changeset, tracer, metadata) do
defp add_validations(changeset, tracer, metadata, actor) do
changeset.resource
# We use the `changeset.action_type` to support soft deletes
# Because a delete is an `update` with an action type of `update`
|> Ash.Resource.Info.validations(changeset.action_type)
|> Enum.reduce(changeset, &validate(&2, &1, tracer, metadata))
|> Enum.reduce(changeset, &validate(&2, &1, tracer, metadata, actor))
end
defp validate(changeset, validation, tracer, metadata) do
defp validate(changeset, validation, tracer, metadata, actor) do
if validation.before_action? do
before_action(changeset, fn changeset ->
if validation.only_when_valid? and not changeset.valid? do
changeset
else
do_validation(changeset, validation, tracer, metadata)
do_validation(changeset, validation, tracer, metadata, actor)
end
end)
else
if validation.only_when_valid? and not changeset.valid? do
changeset
else
do_validation(changeset, validation, tracer, metadata)
do_validation(changeset, validation, tracer, metadata, actor)
end
end
end
defp do_validation(changeset, validation, tracer, metadata) do
defp do_validation(changeset, validation, tracer, metadata, actor) do
if Enum.all?(validation.where || [], fn {module, opts} ->
opts =
Ash.Filter.build_filter_from_template(
opts,
actor,
changeset.arguments,
changeset.context
)
module.validate(changeset, opts) == :ok
end) do
Ash.Tracer.span :validation, "validate: #{inspect(validation.module)}", tracer do
@ -1150,7 +1174,15 @@ defmodule Ash.Changeset do
} do
Ash.Tracer.set_metadata(tracer, :validation, metadata)
case validation.module.validate(changeset, validation.opts) do
opts =
Ash.Filter.build_filter_from_template(
validation.opts,
actor,
changeset.arguments,
changeset.context
)
case validation.module.validate(changeset, opts) do
:ok ->
changeset

View file

@ -565,6 +565,16 @@ defmodule Ash.Filter do
Enum.any?(args, &template_references_actor?/1)
end
def template_references_actor?(list) when is_list(list) do
Enum.any?(list, &template_references_actor?/1)
end
def template_references_actor?(tuple) when is_tuple(tuple) do
tuple
|> Tuple.to_list()
|> Enum.any?(&template_references_actor?/1)
end
def template_references_actor?(_), do: false
@doc false

View file

@ -422,6 +422,14 @@ defmodule Ash.Query do
case module.init(opts) do
{:ok, opts} ->
opts =
Ash.Filter.build_filter_from_template(
opts,
actor,
query.arguments,
query.context
)
case module.prepare(query, opts, %{
actor: actor,
authorize?: authorize?,

View file

@ -21,40 +21,20 @@ defmodule Ash.Resource.Change.SetAttribute do
defp validate_value(_), do: :ok
def change(changeset, opts, _) do
case opts[:value] do
{arg_key, arg} when arg_key in [:arg, :_arg] ->
case Ash.Changeset.fetch_argument(changeset, arg) do
{:ok, value} ->
if opts[:new?] do
if Ash.Changeset.changing_attribute?(changeset, opts[:attribute]) do
changeset
else
Changeset.force_change_attribute(changeset, opts[:attribute], value)
end
else
Changeset.force_change_attribute(changeset, opts[:attribute], value)
end
value =
case opts[:value] do
value when is_function(value) -> value.()
value -> value
end
_ ->
changeset
end
_ ->
value =
case opts[:value] do
value when is_function(value) -> value.()
value -> value
end
if opts[:new?] do
if Ash.Changeset.changing_attribute?(changeset, opts[:attribute]) do
changeset
else
Changeset.force_change_attribute(changeset, opts[:attribute], value)
end
else
Changeset.force_change_attribute(changeset, opts[:attribute], value)
end
if opts[:new?] do
if Ash.Changeset.changing_attribute?(changeset, opts[:attribute]) do
changeset
else
Changeset.force_change_attribute(changeset, opts[:attribute], value)
end
else
Changeset.force_change_attribute(changeset, opts[:attribute], value)
end
end
end

View file

@ -43,7 +43,7 @@ defmodule Ash.Test.Actions.UpdateTest do
update :set_private_attribute_from_arg do
argument :private, :string
change set_attribute(:private, {:arg, :private})
change set_attribute(:private, arg(:private))
end
end
@ -300,8 +300,9 @@ defmodule Ash.Test.Actions.UpdateTest do
profile =
profile
|> new(%{bio: "foobar", private: "blah"})
|> Api.update!(action: :set_private_attribute_from_arg)
|> new(%{bio: "foobar"})
|> for_update(:set_private_attribute_from_arg, %{private: "blah"})
|> Api.update!()
assert profile.private == "blah"
end