Invalid default values

One resource runs into an issue that I haven't had before and cannot solve. I've already updated all dependencies and removed the _build directory without success. The resource has some attributes
attribute :prefix, :string
attribute :suffix, :string
attribute :pattern, :string, allow_nil?: false, default: "{TC}"
attribute :prefix, :string
attribute :suffix, :string
attribute :pattern, :string, allow_nil?: false, default: "{TC}"
which are accepted by the create action. I use a simple generator to create records for testing:
def scheme(opts \\ []) do
{actor, tenant} = actor_and_tenant(opts)

changeset_generator(
Scheme,
:create_scheme,
overrides: opts,
actor: actor,
tenant: tenant
)
end
def scheme(opts \\ []) do
{actor, tenant} = actor_and_tenant(opts)

changeset_generator(
Scheme,
:create_scheme,
overrides: opts,
actor: actor,
tenant: tenant
)
end
When generating a record (generate(scheme(type: :invoice, tenant: organization))), my test sometimes succeed but mostly fail, because I get weird values for the default values:
%Scheme{
id: "ns_0309nvhCcXHLRlmnmLTmIN",
type: :invoice,
prefix: "񔵙",
suffix: nil,
pattern: "{TC}",
}
%Scheme{
id: "ns_0309nvhCcXHLRlmnmLTmIN",
type: :invoice,
prefix: "񔵙",
suffix: nil,
pattern: "{TC}",
}
This happens for all three fields and is not predictable even when using mix test --seed 0.
No description
11 Replies
ZachDaniel
ZachDaniel4mo ago
It generates effectively random values for fields What makes that a problem? Do you have validation somewhere checking the values? Regardless, you can provide the defaults option and customize the default values
zimt28
zimt28OP4mo ago
It's only an issue for the pattern attribute. We checked agains nil first to use a default value first and moved it into the attribute's default value now but that doesn't seem to get used. Shouldn't the default values get used?
ZachDaniel
ZachDaniel4mo ago
Oh, hm...yes you're right What version of Ash are you on?
zimt28
zimt28OP4mo ago
Latest release (3.5.15) I've thought about it and default values are quite tricky, I think. I can see how it's very powerful to test random values and for fields without a default value just setting a nil default seems to be a good compromise, even though it's a little bit counter-intuitive as not passing a value to a regular changeset sets it to nil or the default. When an attribute does have a default value though, it feels more difficult. Currently one would have to set the default value manually, but how could I test the default value then? If this changes and the default value would be used, how could I switch back to random values? One solution I see would be to require an explicit nil default in the generator, which would then use the actual default value to generate the record Maybe there's a more explicit solution like setting :_default but it's quite ugly. With a more explicit solution, maybe another "unset" value could be added as well, so a generator default would be removed? (:_unset or :_random) So
attribute :pattern, :string, allow_nil?: false, default: "{TC}"
attribute :pattern, :string, allow_nil?: false, default: "{TC}"
changeset_generator(
Scheme,
:create_scheme,
defaults: [
pattern: :_default
],
overrides: opts,
)
changeset_generator(
Scheme,
:create_scheme,
defaults: [
pattern: :_default
],
overrides: opts,
)
would create
generate(scheme()).pattern # => "{TC}"
generate(scheme(pattern: :_random)).pattern # => "¯\_(ツ)_/¯"
generate(scheme()).pattern # => "{TC}"
generate(scheme(pattern: :_random)).pattern # => "¯\_(ツ)_/¯"
ZachDaniel
ZachDaniel4mo ago
I think the bug here is simpler We do test defaults IIRC By sometimes including the input and sometimes not So the question is why is it a problem to sometimes not use the default value?
zimt28
zimt28OP4mo ago
Wouldn't this mean that other values never get generated? This seems to be agains the idea of StreamData
ZachDaniel
ZachDaniel4mo ago
No I mean we sometimes generate the map with no key for that value And sometimes with a random value
zimt28
zimt28OP4mo ago
So this means there's no bug and I have to explicitly set the default value in tests when I expect it?
ZachDaniel
ZachDaniel4mo ago
Yeah, I think so?
zimt28
zimt28OP4mo ago
Makes sense. Still, what about a way to "unset" defaults? This would allow to use a generator with a default value and still let it generate any value
ZachDaniel
ZachDaniel4mo ago
Yeah, I think we can reuse the pattern from Ash.Seed
attr_input =
Enum.reduce(input, %{}, fn
{key, :__keep_nil__}, acc ->
Map.put(acc, key, nil)

{_key, :__skip__}, acc ->
acc

{key, value}, acc ->
Map.put(acc, key, value)
end)
attr_input =
Enum.reduce(input, %{}, fn
{key, :__keep_nil__}, acc ->
Map.put(acc, key, nil)

{_key, :__skip__}, acc ->
acc

{key, value}, acc ->
Map.put(acc, key, value)
end)

Did you find this page helpful?