Help with multi-step resource creation in Ash (Phoenix + React)

I’m building a multi‑tenant app with this structure: user → organization → establishment → establishment_user In my flow, a user creates both an organization and an establishment, and the system automatically creates the establishment_user behind the scenes. What I have:
# organization.ex
create :create_organization do
argument :establishment, :map
change relate_actor(:owner)

change after_action(fn changeset, org, context ->
# I’m doing this to return establishment validation errors back to the frontend.
# However, I had to change the attribute names (e.g., "name" → "organization_name"/"establishment_name")
# in both Organization and Establishment resources to differentiate them in my Inertia form—otherwise
# it couldn't map the error to the right field.
# Is this the best approach?
case Lamashka.Establishments.create_establishment(
changeset.arguments.establishment,
actor: context.actor,
tenant: org
) do
{:ok, _establishment} -> {:ok, org}
{:error, error} -> {:error, error}
end
end)
end

# establishment.ex
create :create_establishment do
change after_action(fn _changeset, est, context ->
Lamashka.Establishments.add_establishment_user!(
%{user_id: context.actor.id, role: :admin},
tenant: est
)

{:ok, est}
end)
end

# establishment_user.ex
create :add_establishment_user do
accept [:user_id, :role]
end
# organization.ex
create :create_organization do
argument :establishment, :map
change relate_actor(:owner)

change after_action(fn changeset, org, context ->
# I’m doing this to return establishment validation errors back to the frontend.
# However, I had to change the attribute names (e.g., "name" → "organization_name"/"establishment_name")
# in both Organization and Establishment resources to differentiate them in my Inertia form—otherwise
# it couldn't map the error to the right field.
# Is this the best approach?
case Lamashka.Establishments.create_establishment(
changeset.arguments.establishment,
actor: context.actor,
tenant: org
) do
{:ok, _establishment} -> {:ok, org}
{:error, error} -> {:error, error}
end
end)
end

# establishment.ex
create :create_establishment do
change after_action(fn _changeset, est, context ->
Lamashka.Establishments.add_establishment_user!(
%{user_id: context.actor.id, role: :admin},
tenant: est
)

{:ok, est}
end)
end

# establishment_user.ex
create :add_establishment_user do
accept [:user_id, :role]
end
My questions: - Could I have used manage_relationship/4 for this, or is after_action/3 my only option due to setting different tenants? - Do all three actions run in a single transaction? - How can I implement policies correctly in this setup? With manage_relationship, I could use accessing_from/2 in my policies, but that doesn’t seem to work when using after_action, so I’m bypassing policies entirely for now This setup is working btw (aside from policies), just making sure it’s the right way to do it.
Solution:
1. after_action was the right choice, you can't set a different tenant with manage_relationship AFAIK 2. all run in the same transaction, the first action call opens the transaction and all the after action starts the next action in the same transaction 3 . all accessing_from is checking is a certain value in the context, so you could just manually set the context when calling the other actions inside after_action...
Jump to solution
4 Replies
Solution
barnabasj
barnabasj4mo ago
1. after_action was the right choice, you can't set a different tenant with manage_relationship AFAIK 2. all run in the same transaction, the first action call opens the transaction and all the after action starts the next action in the same transaction 3 . all accessing_from is checking is a certain value in the context, so you could just manually set the context when calling the other actions inside after_action
Joan Gavelán
Joan GavelánOP4mo ago
Nice, this works for policies
change after_action(fn changeset, org, context ->
case Lamashka.Establishments.create_establishment(
changeset.arguments.establishment,
actor: context.actor,
tenant: org,
context: %{
accessing_from: %{
source: __MODULE__,
name: :establishments
}
}
) do
{:ok, _establishment} -> {:ok, org}
{:error, error} -> {:error, error}
end
end)
change after_action(fn changeset, org, context ->
case Lamashka.Establishments.create_establishment(
changeset.arguments.establishment,
actor: context.actor,
tenant: org,
context: %{
accessing_from: %{
source: __MODULE__,
name: :establishments
}
}
) do
{:ok, _establishment} -> {:ok, org}
{:error, error} -> {:error, error}
end
end)
So, is returning the error from the after_action this way correct? I had to rename the name attributes in Organization and Establishment (e.g. organization_name, establishment_name) to differentiate the validation errors, since both originally had the same field name. I’m assuming that’s the only way to handle it—or maybe there’s a better approach?
ZachDaniel
ZachDaniel4mo ago
You can do Ash.Error.set_path(error, :establishment) on the error from the establishment
Joan Gavelán
Joan GavelánOP4mo ago
Niceeeee, that's what I was looking for. Now my react/inertia form is happy and no need for renaming my attributes
{:error, error} -> {:error, Ash.Error.set_path(error, :establishment)}
{:error, error} -> {:error, Ash.Error.set_path(error, :establishment)}
<ErrorField error={errors["establishment.name"]} />
<ErrorField error={errors["establishment.name"]} />
Thank you guys for your help!

Did you find this page helpful?