How to combine polymorphic with references -> on_delete

We have an Ash resource defined as polymorphic:

defmodule  MyApp.Location do

  use Ash.Resource, data_layer: AshPostgres.DataLayer

  postgres do
    repo MyApp.Repo
    polymorphic? true
  end

  attributes do
    uuid_primary_key :id
    attribute :resource_id, :uuid do
      allow_nil? false
      private? true
    end
    attribute :x, :float
    attribute :y, :float
  end

  ...

That is then used by 2 or 3 other resources, e.g.

defmodule  MyApp.Event do

  use Ash.Resource, data_layer: AshPostgres.DataLayer

  postgres do
    repo MyApp.Repo
    table "events"
  end

  attributes do
    uuid_primary_key :id
    attribute :when, :utc_datetime, allow_nil?: false, default: &DateTime.utc_now/0
    attribute :what, :string
    attribute :who, :string
  end

  relationships do
    has_one :location, Location do
      relationship_context %{data_layer: %{table: "event_locations"}}
      destination_attribute :resource_id
    end
  end

Is it possible to use the references config block somehow to delete Locations when the owning Event is deleted?

If it wasn’t polymorphic, we could do something like this in the Location resource, but given it references a specific relationship it won’t work with a polymorphic resource:

  postgres do
    repo MyApp.Repo
    # polymorphic? true

    references do
      reference :event, on_delete: :delete
    end
  end

The obvious (to me) alternative is setting up a destroy action on the Event resource that deletes the associated Location first, but then that has to be repeated for every other resource that may use Location which kinda defeats the purpose of using a polymorphic resource in the first place.

Edit: The destroy action isn’t so easy to get right either - how do you inject the datalayer[:table] context to ensure the delete is applied to the right table?

You can do this to configure the polymorphic relationship.

references do
  polymorphic_on_delete :delete
end
1 Like

That sounds straightforward. So I added that to the Location resource (the polymorphic one), and generated the migrations to update the relationship options in Postgres and no migration was generated:

“No changes detected, so no migrations or snapshots have been created.”

I’ll have a bit more of a play with breaking and recreating the relationships.

Edit: Looks like it needed to go on the opposite side of the relationship, i.e. on the Event resource.

That…doesn’t seem right. I can see where it’s doing that, but it doesn’t really make sense to configure that on the other resource. I think that might be a bug.

Can you open an issue for this in ash? We’ll have to wait until 3.0 to fix it since its a breaking change.

Will do.

There are probably some angles to consider. It makes sense to have it on the current side of the relationship if you want to control delete behaviour on a per relationship basis (i.e. Event → Location you may want the “cascade delete”, but Person → Location you may not). However, if you have relationships to two different polymorphic resources, the current approach doesn’t give enough control for per-relationship behaviour anyway.

If it’s on a per polymorphic resource basis it would make sense to swap it around.

Yeah, thats an good point. Regardless of which side you’re configuring it on you’d want to configure it per polymorphic relationship also. Probably needs to just be reworked in general.

It’s in the GH issue - on reflection, this seems to make sense to me given that the implementation details (e.g. table name) are given at this point:

defmodule MyApp.Event do
  ...
  relationships do
    has_one :location, Location do
       # Data layer context captures implementation details
      relationship_context %{data_layer: %{table: "event_locations", on_delete: :delete}}
       # or relationship_context %{data_layer: %{table: "event_locations"}, on_delete: :delete}
      destination_attribute :resource_id
    end
  end

...