mirror of
https://github.com/ash-project/ash.git
synced 2024-09-19 21:13:10 +12:00
improvement: add match_other_defaults?
to attribute
This commit is contained in:
parent
b71aa3ddeb
commit
394e2d089a
4 changed files with 154 additions and 35 deletions
|
@ -103,6 +103,7 @@ locals_without_parens = [
|
|||
many_to_many: 3,
|
||||
map: 2,
|
||||
map: 3,
|
||||
match_other_defaults?: 1,
|
||||
message: 1,
|
||||
metadata: 2,
|
||||
metadata: 3,
|
||||
|
|
|
@ -816,43 +816,58 @@ defmodule Ash.Changeset do
|
|||
def set_defaults(changeset, action_type, lazy? \\ false)
|
||||
|
||||
def set_defaults(changeset, :create, lazy?) do
|
||||
changeset.resource
|
||||
|> Ash.Resource.Info.attributes()
|
||||
|> Enum.filter(fn attribute ->
|
||||
not is_nil(attribute.default) &&
|
||||
(lazy? or
|
||||
not (is_function(attribute.default) or match?({_, _, _}, attribute.default)))
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn attribute, changeset ->
|
||||
changeset
|
||||
|> force_change_new_attribute_lazy(attribute.name, fn ->
|
||||
default(:create, attribute)
|
||||
attributes = Ash.Resource.Info.attributes(changeset.resource)
|
||||
|
||||
with_static_defaults =
|
||||
attributes
|
||||
|> Enum.filter(fn attribute ->
|
||||
not is_nil(attribute.default) &&
|
||||
not (is_function(attribute.default) or match?({_, _, _}, attribute.default))
|
||||
end)
|
||||
|> Map.update!(:defaults, fn defaults ->
|
||||
[attribute.name | defaults]
|
||||
|> Enum.reduce(changeset, fn attribute, changeset ->
|
||||
changeset
|
||||
|> force_change_new_attribute_lazy(attribute.name, fn ->
|
||||
default(:create, attribute)
|
||||
end)
|
||||
|> Map.update!(:defaults, fn defaults ->
|
||||
[attribute.name | defaults]
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|> Map.update!(:defaults, &Enum.uniq/1)
|
||||
|
||||
if lazy? do
|
||||
set_lazy_defaults(with_static_defaults, attributes, :create)
|
||||
else
|
||||
with_static_defaults
|
||||
end
|
||||
|> Map.update!(:defaults, &Enum.uniq/1)
|
||||
end
|
||||
|
||||
def set_defaults(changeset, :update, lazy?) do
|
||||
changeset.resource
|
||||
|> Ash.Resource.Info.attributes()
|
||||
|> Enum.filter(fn attribute ->
|
||||
not is_nil(attribute.update_default) &&
|
||||
(lazy? or
|
||||
not (is_function(attribute.update_default) or
|
||||
match?({_, _, _}, attribute.update_default)))
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn attribute, changeset ->
|
||||
changeset
|
||||
|> force_change_new_attribute_lazy(attribute.name, fn ->
|
||||
default(:update, attribute)
|
||||
attributes = Ash.Resource.Info.attributes(changeset.resource)
|
||||
|
||||
with_static_defaults =
|
||||
attributes
|
||||
|> Enum.filter(fn attribute ->
|
||||
not is_nil(attribute.update_default) &&
|
||||
not (is_function(attribute.update_default) or
|
||||
match?({_, _, _}, attribute.update_default))
|
||||
end)
|
||||
|> Map.update!(:defaults, fn defaults ->
|
||||
[attribute.name | defaults]
|
||||
|> Enum.reduce(changeset, fn attribute, changeset ->
|
||||
changeset
|
||||
|> force_change_new_attribute_lazy(attribute.name, fn ->
|
||||
default(:update, attribute)
|
||||
end)
|
||||
|> Map.update!(:defaults, fn defaults ->
|
||||
[attribute.name | defaults]
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
if lazy? do
|
||||
set_lazy_defaults(with_static_defaults, attributes, :update)
|
||||
else
|
||||
with_static_defaults
|
||||
end
|
||||
|> Map.update!(:defaults, &Enum.uniq/1)
|
||||
end
|
||||
|
||||
|
@ -860,6 +875,74 @@ defmodule Ash.Changeset do
|
|||
changeset
|
||||
end
|
||||
|
||||
defp set_lazy_defaults(changeset, attributes, type) do
|
||||
changeset
|
||||
|> set_lazy_non_matching_defaults(attributes, type)
|
||||
|> set_lazy_matching_defaults(attributes, type)
|
||||
end
|
||||
|
||||
defp set_lazy_non_matching_defaults(changeset, attributes, type) do
|
||||
attributes
|
||||
|> Enum.filter(fn attribute ->
|
||||
!attribute.match_other_defaults? && get_default_fun(attribute, type)
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn attribute, changeset ->
|
||||
changeset
|
||||
|> force_change_new_attribute_lazy(attribute.name, fn ->
|
||||
default(type, attribute)
|
||||
end)
|
||||
|> Map.update!(:defaults, fn defaults ->
|
||||
[attribute.name | defaults]
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp get_default_fun(attribute, :create) do
|
||||
if is_function(attribute.default) or match?({_, _, _}, attribute.default) do
|
||||
attribute.default
|
||||
end
|
||||
end
|
||||
|
||||
defp get_default_fun(attribute, :update) do
|
||||
if is_function(attribute.update_default) or match?({_, _, _}, attribute.update_default) do
|
||||
attribute.update_default
|
||||
end
|
||||
end
|
||||
|
||||
defp set_lazy_matching_defaults(changeset, attributes, type) do
|
||||
attributes
|
||||
|> Enum.filter(fn attribute ->
|
||||
attribute.match_other_defaults? && get_default_fun(attribute, type)
|
||||
end)
|
||||
|> Enum.group_by(fn attribute ->
|
||||
case type do
|
||||
:create ->
|
||||
attribute.default
|
||||
|
||||
:update ->
|
||||
attribute.update_default
|
||||
end
|
||||
end)
|
||||
|> Enum.reduce(changeset, fn {default_fun, attributes}, changeset ->
|
||||
default_value =
|
||||
case default_fun do
|
||||
function when is_function(function) ->
|
||||
function.()
|
||||
|
||||
{m, f, a} when is_atom(m) and is_atom(f) and is_list(a) ->
|
||||
apply(m, f, a)
|
||||
end
|
||||
|
||||
Enum.reduce(attributes, changeset, fn attribute, changeset ->
|
||||
changeset
|
||||
|> force_change_new_attribute(attribute.name, default_value)
|
||||
|> Map.update!(:defaults, fn defaults ->
|
||||
[attribute.name | defaults]
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp default(:create, %{default: {mod, func, args}}), do: apply(mod, func, args)
|
||||
defp default(:create, %{default: function}) when is_function(function, 0), do: function.()
|
||||
defp default(:create, %{default: value}), do: value
|
||||
|
@ -2228,7 +2311,21 @@ defmodule Ash.Changeset do
|
|||
end
|
||||
|
||||
@doc """
|
||||
Force change an attribute if is not currently being changed, by calling the provided function
|
||||
Force change an attribute if it is not currently being changed
|
||||
|
||||
See `change_new_attribute/3` for more.
|
||||
"""
|
||||
@spec force_change_new_attribute(t(), atom, term) :: t()
|
||||
def force_change_new_attribute(changeset, attribute, value) do
|
||||
if changing_attribute?(changeset, attribute) do
|
||||
changeset
|
||||
else
|
||||
force_change_attribute(changeset, attribute, value)
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Force change an attribute if it is not currently being changed, by calling the provided function
|
||||
|
||||
See `change_new_attribute_lazy/3` for more.
|
||||
"""
|
||||
|
|
|
@ -14,6 +14,7 @@ defmodule Ash.Resource.Attribute do
|
|||
:update_default,
|
||||
:description,
|
||||
:source,
|
||||
match_other_defaults?: false,
|
||||
sensitive?: false,
|
||||
filterable?: true,
|
||||
constraints: []
|
||||
|
@ -136,6 +137,18 @@ defmodule Ash.Resource.Attribute do
|
|||
description: [
|
||||
type: :string,
|
||||
doc: "An optional description for the attribute."
|
||||
],
|
||||
match_other_defaults?: [
|
||||
type: :boolean,
|
||||
default: false,
|
||||
doc: """
|
||||
Ensures that other attributes that use a matching "lazy" default (a default that is a zero argument function), use the same default value.
|
||||
|
||||
Has no effect unless `default` is a zero argument function.
|
||||
|
||||
This is used for timestamps, for example, when their default is `&DateTime.utc_now/0`, we want `created_at` and `updated_at` to be equal to eachoter,
|
||||
instead of separated by the small amount of time between calling both functions.
|
||||
"""
|
||||
]
|
||||
]
|
||||
|
||||
|
@ -143,12 +156,14 @@ defmodule Ash.Resource.Attribute do
|
|||
|> OptionsHelpers.set_default!(:writable?, false)
|
||||
|> OptionsHelpers.set_default!(:private?, true)
|
||||
|> OptionsHelpers.set_default!(:default, &DateTime.utc_now/0)
|
||||
|> OptionsHelpers.set_default!(:match_other_defaults?, true)
|
||||
|> OptionsHelpers.set_default!(:type, Ash.Type.UtcDatetimeUsec)
|
||||
|> OptionsHelpers.set_default!(:allow_nil?, false)
|
||||
|
||||
@update_timestamp_schema @schema
|
||||
|> OptionsHelpers.set_default!(:writable?, false)
|
||||
|> OptionsHelpers.set_default!(:private?, true)
|
||||
|> OptionsHelpers.set_default!(:match_other_defaults?, true)
|
||||
|> OptionsHelpers.set_default!(:default, &DateTime.utc_now/0)
|
||||
|> OptionsHelpers.set_default!(
|
||||
:update_default,
|
||||
|
|
|
@ -206,6 +206,8 @@ defmodule Ash.Test.Actions.CreateTest do
|
|||
items: [min: -10, max: 10]
|
||||
]
|
||||
)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
relationships do
|
||||
|
@ -261,11 +263,6 @@ defmodule Ash.Test.Actions.CreateTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "upserts" do
|
||||
test "allows upserting a record using an identity" do
|
||||
end
|
||||
end
|
||||
|
||||
describe "simple creates" do
|
||||
test "allows creating a record with valid attributes" do
|
||||
assert %Post{title: "foo", contents: "bar"} =
|
||||
|
@ -280,6 +277,15 @@ defmodule Ash.Test.Actions.CreateTest do
|
|||
|> Api.create!()
|
||||
end
|
||||
|
||||
test "timestamps will match each other" do
|
||||
post =
|
||||
Post
|
||||
|> for_create(:create, %{title: "foobar"})
|
||||
|> Api.create!()
|
||||
|
||||
assert post.inserted_at == post.updated_at
|
||||
end
|
||||
|
||||
test "allow_nil validation" do
|
||||
{:error, error} =
|
||||
Post
|
||||
|
|
Loading…
Reference in a new issue