Skip to content

Instantly share code, notes, and snippets.

@zachdaniel
Created November 6, 2021 15:22
Show Gist options
  • Save zachdaniel/e5cfade3c5ddd8b60c198d35c1c297e5 to your computer and use it in GitHub Desktop.
Save zachdaniel/e5cfade3c5ddd8b60c198d35c1c297e5 to your computer and use it in GitHub Desktop.
Check if an AshPhoenix Form is changed
defp changed?(form) do
is_changed?(form) ||
Enum.any?(form.forms, fn {_key, forms} ->
forms
|> List.wrap()
|> Enum.any?(&changed?/1)
end)
end
defp is_changed?(form) do
if form.type == :create do
true
else
attributes_changed?(form) || arguments_changed?(form)
end
end
defp attributes_changed?(%{source: %Ash.Query{}}), do: false
defp attributes_changed?(form) do
changeset = form.source
form_keys = update_forms(form.form_keys)
changeset.attributes
|> Map.drop(Enum.map(form_keys, &elem(&1, 0)))
|> Map.delete(:last_editor_save)
|> Enum.any?(fn {key, value} ->
original_value = Map.get(changeset.data, key) || default(changeset.resource, key)
Comp.not_equal?(value, original_value)
end)
end
def arguments_changed?(form) do
changeset = form.source
form_keys = update_forms(form.form_keys)
changeset.arguments
|> Map.drop(Enum.map(form_keys, &elem(&1, 0)))
|> Enum.any?(fn {key, value} ->
action =
if is_atom(changeset.action) do
Ash.Resource.Info.action(changeset.resource, changeset.action)
else
changeset.action
end
original_value = default_argument(action, key)
value != original_value
end)
end
# if the value is the same as the default, we don't want to consider it as changed
defp default_argument(action, key) do
action.arguments
|> Enum.find(&(&1.name == key))
|> case do
nil ->
nil
argument ->
cond do
is_nil(argument.default) ->
nil
is_function(argument.default) ->
argument.default.()
true ->
argument.default
end
end
end
defp default(resource, key) do
attribute = Ash.Resource.Info.attribute(resource, key)
cond do
is_nil(attribute.default) ->
nil
is_function(attribute.default) ->
attribute.default.()
true ->
attribute.default
end
end
# To prevent infinite loops when building configuration, each level is constructed as some keys
# along w/ an `updater` key. Here we call it if there is one. If this was done inside of `AshPhoenix.Form`,
# it would save any produced form options along w/ each form when checking, and save a `.changed?` key whenever
# the form was validated/created (since we have to traverse all the input anyway, this wouldn't be expensive.
defp update_forms(form_keys) do
form_keys
|> Enum.map(fn
{key, opts} ->
if opts[:updater] do
{key, Keyword.delete(opts[:updater].(opts), :updater)}
else
{key, opts}
end
end)
|> Enum.uniq_by(&elem(&1, 0))
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment