Destroying related resources

axdc
2023-04-14

axdc:

I have some related resources with existing actions utilizing manage_relationship to create and update them. For destroying, is it pretty much the same process, where the form has all the nested subforms and then I take those in through arguments and manage_relationship them and destroy them? I haven’t seen anything explicitly on this topic in the documentation or here, pardon me if I’ve missed it. I want to make sure I don’t leave any orphaned resources, which the loaded form on my edit page should take care of, right, but I’m wondering if there’s a way you’re supposed to do it directly in the action to be sure.

kernel:

if you want to be 100% certain, you can make sure you use Foreign Keys and cascade deletes

axdc:

I’m ~not familiar with how to instrument that from within Ash. The setup I have now is edit.ex

...
 def handle_event("destroy", %{}, socket) do
    destroy_form =
      AshPhoenix.Form.for_destroy(socket.assigns.site, :destroy_site,
        api: Panacea.Sites,
        forms: [auto?: true],
        actor: socket.assigns.current_user
      )
      |> to_form

    case AshPhoenix.Form.submit(destroy_form) do
      :ok ->
        {:noreply,
         socket
         |> put_flash(:info, "Site destroyed successfully")
         |> push_navigate(to: ~p"/commander/sites")}

      {:error, error} ->
        IO.inspect(error)

        {:noreply,
         socket
         |> put_flash(:error, "There was a problem destroying this site")
         |> push_patch(to: ~p"/commander/sites/#{socket.assigns.site}/edit")}
    end
  end
...

site.ex

...
  # destroy site and accompanying resources
    destroy :destroy_site do
      argument :destroy_configuration, :map do
        allow_nil? false
      end

      argument :destroy_domains, {:array, :map} do
        allow_nil? false
      end

      argument :destroy_profiles, {:array, :map} do
        allow_nil? false
      end

      change manage_relationship(:destroy_configuration, :configuration, on_match: :destroy)
      change manage_relationship(:destroy_domains, :domains, on_match: :destroy)
      change manage_relationship(:destroy_profiles, :profiles, on_match: :destroy)
    end
...

And I’m getting the error:

errors: [
    destroy_profiles: {"is required", []},
    destroy_domains: {"is required", []},
    destroy_configuration: {"is required", []}
  ],

The form appears to have those subforms in it when I IO.inspect it, so I’m a bit perplexed. Maybe I’m keying it wrong? It mostly matches the setup for my create and edit actions right now, except without an actual form you can edit in the template itself, since we’re just destroying.

ZachDaniel:

Ah, yeah so this isn’t how you want to do this

ZachDaniel:

postgres do
  references do
    reference, :configuration, on_delete: :delete
    reference, :domains, on_delete: :delete
    reference, :profiles, on_delete: :delete
  end
end

ZachDaniel:

Then generate migrations

ZachDaniel:

This will tell the database to destroy those things whenever the parent thing is destroyed

axdc:

Okay yeah that’s way more modelly and Ash-y. I figured there would be something nice like that. When I drop and recreate the database with migrations and all, it’s still telling me the delete can’t be performed because it would leave records behind:

%Ash.Error.Invalid{
  errors: [
    %Ash.Error.Changes.InvalidAttribute{
      field: :id,
      message: "would leave records behind",
      private_vars: [
        constraint: :foreign,
        constraint_name: "profiles_site_id_fkey"
      ],
      value: nil,
      changeset: nil,
      query: nil,
      error_context: [],
      vars: [],
      path: [],
      stacktrace: #Stacktrace<>,
      class: :invalid
    }
  ],

Specifically calling out Profiles. Is there more I should be aware of when constructing these relationships? I’m reading through everything I can find on references on ashhq now, thank you!!

ZachDaniel:

That’s the primary thing, configuring the references let’s you do pretty much everything you should need for this case

axdc:

Okay so it looks like the references are supposed to be defined on each child, not on the parent, so I had that backwards initially. That was my misunderstanding which I corrected by looking through the realworld source here: https://github.com/team-alembic/realworld/search?q=reference I then defined the references block on each of the child resources of the Site (Domains, Configuration, etc) and now they all tidily happily disappear when their Site is destroyed. So I think I get it now. Thank you 🥲

\ ឵឵឵:

Is there a builtin to accomplish the same at the Ash layer, for the case where not all resources are in Postgres?

ZachDaniel:

I don’t believe so, no