carlgleisner

carlgleisner

Hi all,

I’m creating a resource that represents entities that can be either:

  • :natural persons or
  • :legal persons.

These have most attributes and actions in common and will primarily have to meet the need of having relationships along the lines of claim - belongs_to -> entity where the entity can be of either type. Of course, one would like to be able to query all entities and have their proper full name returned and so on.

I’m strongly suspecting that I should create a polymorphic relationship as described in the guides with an Entity resource and NaturalPerson and LegalPerson resources respectively, but a preliminary question has come to mind while evaluating the actual need for that.

If one were to have a resource Entity without that kind of polymorphism, could one have different :create actions that accept different attributes for the kinds of names that are then stored in :name1, :name2 and :name3 attributes on the Entity resource? Or would one “just” handle that in the UI?


  • :name1:first_name for :natural_person:s and :company_name for :legal_person:s:
  • :name2:middle_name for :natural_person:s
  • :name3:last_name for :natural_person:s

When for instance creating entities:s, I’d like to – somehow! – accept :first_name, :middle_name and :last_name in a :create_natural_person action and :company_name in a :create_legal_person action.

That does not feel very Ash:y however and I guess that it can lead to issues down the line.


As I noted above, I strongly suspect that this is a case of polymorphism.

Asking for a friend etc. :otter:

Thank you in advance good people!

Kind regards,
Carl

Showing Posts 1 to 6

zachdaniel

zachdaniel

Creator of Ash

You can definitely do it with actions. Their job is to accept whatever inputs and encapsulate some given domain operation:

create :create_natural_person do
  argument :first_name, :string
  argument :middle_name, :string
  ...

  change set_attribute(:name1, arg(:first_name))
  change set_attribute(:name2, arg(:middle_name))
  change set_attribute(:type, :natural)
end

create :create_legal_person do
  argument :company_name, :string

  change set_attribute(:name1, arg(:company_name))
  change set_attribute(:type, :legal)
end

The actions guide and changes guide should have more examples, set_attribute/2 is a builtin but you can write your own.

carlgleisner

carlgleisner OP

That’s really great, sometimes it’s simpler than one thinks – thank you! :folded_hands:t2:

carlgleisner

carlgleisner OP

Thanks again and that’s up and running for :create actions now, with really nifty calculations and all :pinched_fingers:

But for updates such as :update_natural_person I couldn’t quite figure out the plumbing of it all.

My first thought was to create a custom change that gets the attributes and populates the arguments:

change fn changeset, _context ->
  case Ash.Changeset.get_attribute(changeset, :type) do
    :natural ->
      first_name = Ash.Changeset.get_attribute(changeset, :name1)
      middle_name = Ash.Changeset.get_attribute(changeset, :name2)
      last_name = Ash.Changeset.get_attribute(changeset, :name3)

      changeset
      |> Ash.Changeset.set_argument(:first_name, first_name)
      |> Ash.Changeset.set_argument(:middle_name, middle_name)
      |> Ash.Changeset.set_argument(:last_name, last_name)

    :legal ->
      # TBD
      changeset
  end
end

which indeed does populates the arguments, in AshAdmin, with the current data. But upon any revalidation, one gets errors on the arguments.

Also then tried combining this with force_change_attribute/3 at the end of the action to set the attributes from the arguments, but I realise that it’s biting myself in the behind since I’m getting the old arguments back and no changes are persisted. So there is a lifecycle challenge here obviously. Couldn’t figure it out from the lifecycle documentation unfortunately.

To exhaust all available options before asking I did consult an ML-tool that generated a response “suggesting” that one should use prepare_changes :laughing: I don’t think that is an Ash function. Please note that I never vibe code! Need to exercise by brain (obviously).

Option B: Define a prepare_changes that backfills arguments (:warning: hacky)

If there is an obvious approach here I would be most grateful for any guidance of course.

zachdaniel

zachdaniel

Creator of Ash

:thinking: that should work I think? What kind of errors do you get on the arguments?

I would say that it might be easier to handle this in your LiveView though, using value={AshPhoenix.Form.value(form, :name1)} etc.

You might need to only set the argument if its not already set for example.

carlgleisner

carlgleisner OP

Thanks for getting back!

The validation errors are just “required” but the action goes through since the arguments have (some) value after all. I have to |> dbg()-look at this with fresh eyes in the morning with a cuppa joe to make complete sense :hot_beverage:

Will revert with findings, including LiveView handling, in the hope that they prove useful for the community :slight_smile:

carlgleisner

carlgleisner OP

Got it working by only setting the argument – from data this time – if the argument is nil.

But there is still a validation issue that might deserve a new changset function remove_error/3.

change SetUpdateArguments
@impl true
def change(changeset, _opts, _context) do
  case Ash.Changeset.get_attribute(changeset, :type) do
    :natural ->
      changeset
      |> maybe_set_argument(:first_name, :name1)
      |> maybe_set_argument(:middle_name, :name2)
      |> maybe_set_argument(:last_name, :name3)

    :legal ->
      changeset
      |> maybe_set_argument(:company_name, :name1)
  end
end

defp maybe_set_argument(changeset, argument, attribute) do
  if Ash.Changeset.get_argument(changeset, argument) do
    changeset
  else
    changeset
    |> Ash.Changeset.set_argument(
      argument,
      Ash.Changeset.get_attribute(changeset, attribute)
    )
  end
end

This pre-populates the arguments and the set_argument/2 doesn’t get in the way of provided changes (someone was tired yesterday).

But the thing is that the changeset has validation errors for all the required arguments from the get-go:

[(my_app 0.1.0) lib/my_app/entities/actor/changes/set_update_arguments.ex:25: MyApp.Entities.Actor.Changes.SetUpdateArguments.change/3]
changeset #=> #Ash.Changeset<
  domain: MyApp.Entities,
  action_type: :update,
  action: :update_natural_person,
  tenant: "null",
  attributes: %{},
  relationships: %{},
  arguments: %{first_name: "Kurt", middle_name: "Arne", last_name: "Olsson"},
  errors: [
    %Ash.Error.Changes.Required{
      field: :last_name,
      type: :argument,
      resource: MyApp.Entities.Actor,
      splode: nil,
      bread_crumbs: [],
      vars: [],
      path: [],
      stacktrace: #Splode.Stacktrace<>,
      class: :invalid
    },
    %Ash.Error.Changes.Required{
      field: :first_name,
      type: :argument,
      resource: MyApp.Entities.Actor,
      splode: nil,
      bread_crumbs: [],
      vars: [],
      path: [],
      stacktrace: #Splode.Stacktrace<>,
      class: :invalid
    }
  ],
  data: %MyApp.Entities.Actor{
    id: "019870c3-ed58-76a7-9a21-b7948c72e72c",
    type: :natural,
    name1: "Kurt",
    name2: "Arne",
    name3: "Olsson",
    inserted_at: ~U[2025-08-03 16:29:05.240479Z],
    updated_at: ~U[2025-08-04 05:30:37.616108Z],
    name: "Kurt Arne Olsson",
    formal_name: "Olsson, Kurt Arne",
    __meta__: #Ecto.Schema.Metadata<:loaded, "actors">
  },
  valid?: false
>

So when touching the form in AshAdmin the validation errors show even though the changeset reasonably should be valid.

Before touching:

After touching:

Maybe it would be hacky, but wouldn’t it be possible to have a remove_error/3 akin to add_error/3? I noted that add_error/3 also sets the changeset to invalid – maybe it’s impossible to say whether it should be valid or invalid in that case? But if errors are empty after pre-populating the arguments one could perhaps dare to mark the changeset as valid?

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews