Ash 3.4.48 breaks the example in "Define Polymorphic Relationships" document

I got one question and one issue in the example in the Define Polymorphic Relationships document, I created a repository for easy debugging/reproducing: https://github.com/iyjim/toy_bank.git

Question:
The syntax in th constraints of the union type

constraints: [
    checking: [
      type: :struct,
      constraints: [instance_of: CheckingAccount]
    ],
    savings: [
      type: :struct,
      constraints: [instance_of: SavingsAccount]
    ]
  ]

is different from all other examples I could find, like this one

constraints: [
    types: [
      one: [type: YourEmbed],
      other: [type: YourOtherEmbed]
    ]
  ]

in Does Ash supports polymorphic embedded resources?
And it will get

== Compilation error in file lib/bank_account.ex ==
** (UndefinedFunctionError) function CheckingAccount.init/1 is undefined or private. Did you mean:

      * input/1
      * input/2

    CheckingAccount.init([])
    (ash 3.4.47) lib/ash/type/union.ex:73: anonymous fn/2 in Ash.Type.Union.init/1
    (elixir 1.18.0) lib/enum.ex:4964: Enumerable.List.reduce/3
    (elixir 1.18.0) lib/enum.ex:2600: Enum.reduce_while/3
    (ash 3.4.47) lib/ash/type/union.ex:68: Ash.Type.Union.init/1
    (ash 3.4.47) lib/ash/type/type.ex:2025: Ash.Type.set_type_transformation/1
    (spark 2.2.36) lib/spark/dsl/entity.ex:273: Spark.Dsl.Entity.build/3
    (ash 3.4.47) /home/jim/ex18/toy_bank/deps/spark/lib/spark/dsl/extension.ex:1150: Ash.Resource.Dsl.Calculations.Calculate.__build__/3

if using the second syntax mentioned above. But it seems that the second syntax is the correct one? https://hexdocs.pm/ash/Ash.Type.Union.html

Issue:
Got a compilation error if ash is 3.4.48 and above (using the first syntax)

== Compilation error in file lib/bank_account.ex ==
** (Spark.Error.DslError) [BankAccount]
calculations -> calculate -> implementation:
  types must be a list, got ``
    (ash 3.4.49) /home/jim/ex18/toy_bank/deps/spark/lib/spark/dsl/extension.ex:1174: Ash.Resource.Dsl.Calculations.Calculate.__build__/3
    (spark 2.2.36) lib/spark/dsl/extension/entity.ex:91: Spark.Dsl.Extension.Entity.handle/6
    lib/bank_account.ex:23: (module)

And got another compilation error if using the second syntax

== Compilation error in file lib/bank_account.ex ==
** (UndefinedFunctionError) function CheckingAccount.init/1 is undefined or private. Did you mean:

      * input/1
      * input/2

    CheckingAccount.init([])
    (ash 3.4.49) lib/ash/type/union.ex:74: anonymous fn/2 in Ash.Type.Union.init/1
    (elixir 1.18.0) lib/enum.ex:4964: Enumerable.List.reduce/3
    (elixir 1.18.0) lib/enum.ex:2600: Enum.reduce_while/3
    (ash 3.4.49) lib/ash/type/union.ex:69: Ash.Type.Union.init/1
    (ash 3.4.49) lib/ash/type/type.ex:2025: Ash.Type.set_type_transformation/1
    (spark 2.2.36) lib/spark/dsl/entity.ex:273: Spark.Dsl.Entity.build/3
    (ash 3.4.49) /home/jim/ex18/toy_bank/deps/spark/lib/spark/dsl/extension.ex:1150: Ash.Resource.Dsl.Calculations.Calculate.__build__/3

What should I do to overcome this issue?

Please help.

This is a typo in the docs, sorry about that. The proper definition would be

defmodule AccountImplementation do
  use Ash.Type.NewType,
    subtype_of: :union,
    constraints: [
      types: [
        checking: [
          type: :struct,
          constraints: [instance_of: CheckingAccount]
        ],
        savings: [
          type: :struct,
          constraints: [instance_of: SavingsAccount]
        ]
      ]
    ]
end

The second syntax is used for embedded resources, which is not the case in this example. I will update the docs

Thank you @zachdaniel for your prompt reply! It save my life!

2 Likes

@zachdaniel I got one more question about this example, please help:

Whenever we want to create a savings_account, we’ll have to create a bank_account first.

I thought I can create a bank_account in a :create action of SavingsAccount, but when I use “change”, to create a bank_account first in a :create action, it will be called once in AshPhoenix.Form.for_create(). If I then cancel the new SavingsAccountLive.FormComponent, the bank_account just created is no longer useful.

Is there appropriate way here?

Look into Nested Forms. I think what you want is to manage_relationship on creating bank account to also relate a savings account to it.

1 Like

You should create the bank account in before or after action hook. See Ash.Changeset.before_action and after_action if done with manage_relationship as @ken-kost points out, this happens automatically.

1 Like

Thanks @ken-kost & @zachdaniel , I just tried:
In the :create action in SavingsAccount:

change manage_relationship(:bank_account_id, : bank_account, type: :append)

and in AshPhoenix.Form.for_create(…):

prepare_source: fn x →
x
|> Ash.Changeset.before_action(fn y →
{:ok, bank_account2} = Ash.create(BankAccount, %{account_number: 2, type: :savings})
y
|> Ash.Changeset.force_change_attribute(:bank_account_id, bank_account2.id)
end)
end,

and it works! :tada::tada::tada:

1 Like