AshJsonApi + Managed Relationships

psychonaugty
2023-07-17

psychonaugty:

Are there any examples of AshJsonApi, the docs seem outdated. Im trying to get managed relationships to work through a ‘parent’ resource.

psychonaugty:

This is my current setup for a “Location” resource and an “Area” managed relationship:

...Location resouce which has many areas

    relationships do
      has_many :areas, Area
    end

    json_api do
      type "location"
  
      includes(areas: [])
  
      routes do
        base("/locations")
  
        get(:read)
        index :read
        post(:create)
        delete(:destroy)
  
        related(:areas, :read)
  
        relationship(:areas, :read)
  
        post_to_relationship(:areas)
        patch_relationship(:areas)
        delete_from_relationship(:areas)
      end
    end

    update :update do
      argument :areas, :map do
        allow_nil? false
      end

      change manage_relationship(:areas, type: :create)
    end

psychonaugty:

When trying to get http://localhost:4000/api/locations/55b9cd3c-8a83-431e-b580-98365bc2b5ab/relationships/areas , I get a 500

psychonaugty:

psychonaugty:

This is the Area resource

...Area resource


  json_api do
    type "area"

    routes do
      base("/areas")

      get(:read)
      index :read
      post(:create, relationship_arguments: [{:id, :location}])
      patch(:update, relationship_arguments: [:location])
      delete(:destroy)

      related :location, :read do
        primary? true
        route("/:id/location")
      end

      relationship :location, :read do
        primary? true
      end
    end
  end

  relationships do
    belongs_to :location, Location, allow_nil?: false
  end

zachdaniel:

Oh, okay that looks like a bug that may actually have been fixed already

zachdaniel:

are you on the latest version of ash_json_api ?

zachdaniel:

If so, can you try pointing at main? github: "ash-project/ash_json_api", ref: "main" ?

psychonaugty:

<@197905764424089601> That works! Although it does not return any of its fields, just an id and type

zachdaniel:

🤔

zachdaniel:

oh, yeah that is correct

zachdaniel:

relationship/2 creates an endpoint designed to do that

zachdaniel:

if you want an endpoint that returns the full related entities thats when you use related/2

zachdaniel:

which is typically the difference between /thing/:id/relationships/related and /thing/:id/related

psychonaugty:

hm, tried http://localhost:4000/api/locations/55b9cd3c-8a83-431e-b580-98365bc2b5ab/areas but got a 404?

psychonaugty:

sorry, actually worked!

psychonaugty:

Not getting related links for included relationships though, is that right?

zachdaniel:

looks like a get_related route needs to be marked as primary to show up there

  defp add_related_link(links, request, %resource{} = record, relationship) do
    resource
    |> AshJsonApi.Resource.route(%{
      relationship: relationship.name,
      primary?: true,
      action_type: :get_related
    })
    |> case do
      nil ->
        links

      %{route: route} ->
        link =
          request
          |> with_path_params(%{"id" => AshJsonApi.Resource.encode_primary_key(record)})
          |> at_host(route)

        Map.put(links, "related", link)
    end
  end

zachdaniel:

So if you set the get_related to primary? true then it should appear in the links

zachdaniel:

its been a long time since I made that choice, but I imagine it was to solve for cases where there were multiple endpoints to get the same relationship

zachdaniel:

That could probably be improved 🙂

psychonaugty:

That worked too!

psychonaugty:

Im trying to patch a managed resource through the main one, eg: http://localhost:4000/api/locations/55b9cd3c-8a83-431e-b580-98365bc2b5ab

Curl eg:

PATCH /api/locations/55b9cd3c-8a83-431e-b580-98365bc2b5ab HTTP/1.1
x-api-key: Yc8ccgMNfDcTCDGKWdotZ+1aemcJswQq
x-tenant-id: ef19b3ea-99d8-4fe4-9622-af6931379c6d
Content-Type: application/vnd.api+json
Host: localhost:4000
Connection: close
User-Agent: RapidAPI/4.2.0 (Macintosh; OS X/13.4.1) GCDHTTPRequest
Content-Length: 148

{"data":{"type":"location","relationships":{"areas":{"data":{"type":"area","name":"Train Station X"}}},"id":"55b9cd3c-8a83-431e-b580-98365bc2b5ab"}}

Any idea how that works? I have this on the main resource

      get(:read)
      index :read
      post(:create)
      patch(:update)
      delete(:destroy)

      related :areas, :read do
        primary? true
      end

      relationship(:areas, :read)

      post_to_relationship(:areas)
      patch_relationship(:areas)
      delete_from_relationship(:areas)

zachdaniel:

You provide it as an attribute in that case

zachdaniel:

the map of data to be passed into the managed relationship I mean

zachdaniel:

we pull arguments and attributes out of the attributes field (because JSON:API doesn’t give us anywhere else to accept input AFAIK)

zachdaniel:

released 0.32.1 so you don’t need to be on a GH branch 🙂

psychonaugty:

Looking at the spec for jsonapi here: https://jsonapi.org/format/#crud-updating-resource-relationships Looks like you can update from passing it as a relationship on patch?

zachdaniel:

actually yeah it looks like we will search for arguments meant to manage relationships

zachdaniel:

ah, thats right, its configured

zachdaniel:

    routes do
      patch :update, relationship_arguments: [:authors]
    end

zachdaniel:

You say which arguments are edited in the relationships