ash-project / ash_json_api

The JSON:API extension for the Ash Framework
https://hexdocs.pm/ash_json_api
MIT License
56 stars 40 forks source link

Issue with Removing a Resource through Relationship with Composite Primary Key #206

Closed diogomrts closed 2 months ago

diogomrts commented 2 months ago

Hello, I've encountered an issue when trying to delete a DashboardWidget through a relationship using a composite primary key. I'm not sure if I'm using it incorrectly, but the behavior seems off.

I followed an example from the Ash JSON API repository:
Example from patch_test.exs

These are my two Resources:

defmodule App.Core.Dashboard do
  use Ash.Resource,
    domain: App.Core,
    extensions: [
      AshJsonApi.Resource,
    ]

  json_api do
    type "dashboard"

    includes([:dashboard_widgets])

    routes do
      base("/dashboards")

      get(:read)
      index :read
      patch(:update)
      patch(:delete_dashboard_widgets, route: "/:id/dashboard_widgets/delete")
    end
  end

  postgres do
    table "widgets_users"
    repo App.Repo
  end

  attributes do
    integer_primary_key :id
    attribute :user_id, :uuid

    attribute :name, :string, public?: true
    attribute :is_default, :boolean, default: false, public?: true
    attribute :contents, :string, public?: true
  end

  relationships do
    belongs_to :user, App.Auth.User, domain: App.Auth

    has_many :dashboard_widgets, App.Core.DashboardWidget do
      public? true
      source_attribute :id
      destination_attribute :dashboard_id
    end
  end

  actions do
    read :read do
      primary? true
    end

    update :update do
      accept [:name, :contents]
    end

    update :delete_dashboard_widgets do
      accept []
      require_atomic? false

      argument :dashboard_widget_ids, {:array, :map}, allow_nil?: false

      change manage_relationship(:dashboard_widget_ids, :dashboard_widgets, type: :remove)
    end
  end
end
defmodule App.Core.DashboardWidget do
  use Ash.Resource,
    domain: App.Core,
    extensions: [
      AshJsonApi.Resource,
    ]

  json_api do
    type "dashboard_widget"

    primary_key do
      keys([:dashboard_id, :widget_id])
    end

    routes do
      related(:dashboard, :read)
      patch_relationship(:dashboard)
    end
  end

  attributes do
    attribute :dimensions_width, :integer, public?: true
    attribute :dimensions_height, :integer, public?: true
    attribute :position_x, :integer, public?: true
    attribute :position_y, :integer, public?: true
    attribute :moved, :boolean, public?: true
    attribute :static, :boolean, public?: true
  end

  relationships do
    belongs_to :dashboard, App.Core.Dashboard,
      primary_key?: true,
      allow_nil?: false,
      attribute_type: :integer,
      public?: true

    belongs_to :widget, App.Core.Widget, primary_key?: true, allow_nil?: false
  end

  actions do
    defaults [:read]

    update :update do
      primary? true
      accept [:dimensions_width, :dimensions_height, :position_x, :position_y, :moved, :static]
      argument :dashboard, :map
      require_atomic? false

      change manage_relationship(:dashboard, type: :append_and_remove)
    end
  end
end

In my case, since I have a composite primary key with dashboard_id and widget_id, I passed "1-042c2d7d-af6d-4edf-a6f1-fc8adce0d6cf" to the request dashboards/1/dashboard_widgets/delete:

{
  "data": {
    "attributes": {
      "dashboard_widget_ids": [
        "1-042c2d7d-af6d-4edf-a6f1-fc8adce0d6cf"
      ]
    }
  }
}

But there is an error:

# BadMapError at PATCH /v1/dashboards/1/dashboard_widgets/delete

Exception:

    ** (BadMapError) expected a map, got: "1-042c2d7d-af6d-4edf-a6f1-fc8adce0d6cf"
        (stdlib 4.3.1.4) :maps.find(:widget_id, "1-042c2d7d-af6d-4edf-a6f1-fc8adce0d6cf")
        (ash 3.0.16) lib/ash/actions/managed_relationships.ex:1535: Ash.Actions.ManagedRelationships.fetch_field/2
        (ash 3.0.16) lib/ash/actions/managed_relationships.ex:1508: Ash.Actions.ManagedRelationships.do_matches?/4
        (elixir 1.16.3) lib/enum.ex:4199: Enum.predicate_list/3
        (elixir 1.16.3) lib/enum.ex:4288: Enum.find_list/3
        (ash 3.0.16) lib/ash/actions/managed_relationships.ex:797: Ash.Actions.ManagedRelationships.handle_input/10

So i tried changing the argument to a map containing the two IDs. However, that also did not work.

Additional Context: To avoid this, I considered defining an endpoint in the DashboardWidget resource. However, this approach would not provide the full dashboard structure in the response, which is something I need.

** Runtime

zachdaniel commented 2 months ago

As far as I can tell, this should be working. However, you don't pass the composite id, you'll have to pass the full map. I've added a test to the main branch in that same test file you were looking in. as an example.