AshGraphql: Updating a managed has_many relationship

moxley
2023-03-22

moxley:

I’m getting "In field \"id\": Unknown field." in the GraphQL response error. It’s referring to the id provided in the has_many related record .

# This is the updateWebSite input argument. It's a UpdateWebSiteInput type.
input = %{
  # The has_many relationship
  components: [
    %{
      # This is the primary key of the associated record (UpdateWebSiteComponentsInput)
      id: "38f61258-0e8f-4a99-8bae-0cb892e6ccaa",
      value: "new value",
      key: "test-component",
    }
  ]
}

Here are the relevant parts of my WebSite resource:

actions do
  # Add a set of simple actions. You'll customize these later.
  defaults [:read, :destroy]

  update :update do
    argument :components, {:array, :map}

    # I'm not sure what options to pass here.
    # These current options I got from looking at the AshGraphql tests
    change manage_relationship(
              :components,
              type: :direct_control,
              on_lookup: :relate,
              on_no_match: :create
            )
  end
end
  
managed_relationships do
  managed_relationship :create, :components

  # I'm not sure what options to pass here
  managed_relationship :update, :components
  # managed_relationship :update, :components, lookup_with_primary_key?: true
end

I tried playing with the options both for change manage_relationship() and managed_relationship() . I got different errors. I’m not quite understanding the documentation yet.

ZachDaniel:

Have you had a look at the generated types for those managed_relationship fields?

ZachDaniel:

like in the graphql playground?

ZachDaniel:

The options for manage_relationship are discussed here

ZachDaniel:

There are a lot of considerations to be made on that front. What exactly should happen with the values provided in the components key?

ZachDaniel:

You likely want lookup_with_primary_key?: true in your managed_relationship to cause it to add the :id field if you’re doing something that should lookup/update existing records. What exactly ought to happen with the input you provide to components? What if something is left out of the list? Should it be removed?

moxley:

The options for manage_relationship are discussed here

Yep, I read that. There’s a lot of read. I’m having difficulty understanding it.

There are a lot of considerations to be made on that front. What exactly should happen with the values provided in the components key?

The list of values in the components key are the attributes for each component under a WebSite. These should be used as the attributes to insert or update each component with. The id field in each item is the component’s ID. It should be used to look up the component record in the database for updating. If no ID is given, a new record should be inserted. If the WebSite has a component whose ID is not in the list of component items, that record should be deleted. This is the behavior I’m used to working with when using Ecto’s cast_assoc for has_many relationships.

moxley:

When adding lookup_with_primary_key?: true to the managed_relationship call, there’s a compiler error: ** (Protocol.UndefinedError) protocol Enumerable not implemented for %Absinthe.Blueprint.Schema.FieldDefinition{name: "id", identifier: :id...

ZachDaniel:

you’re having a time of it

ZachDaniel:

Got it, so you want type: :direct_control

ZachDaniel:

whats the stack trace for that error?

moxley:

I’m using :direct_control

moxley:

== Compilation error in file lib/gf_web/graphql/absinthe_schema.ex ==
** (Protocol.UndefinedError) protocol Enumerable not implemented for %Absinthe.Blueprint.Schema.FieldDefinition{name: "id", identifier: :id, type: :id, module: GfWeb.GraphQL.AbsintheSchema, description: nil, deprecation: nil, config: nil, triggers: [], default_value: nil, arguments: [], directives: [], complexity: nil, source_location: nil, middleware: [], function_ref: nil, flags: %{}, errors: [], __reference__: %{location: %{file: "/Users/mstratto/work/moxley/gf-core/deps/ash_graphql/lib/resource/resource.ex", line: 2847}, module: AshGraphql.Resource}, __private__: []} of type Absinthe.Blueprint.Schema.FieldDefinition (a struct)
    (elixir 1.14.3) lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir 1.14.3) lib/enum.ex:166: Enumerable.reduce/3
    (elixir 1.14.3) lib/enum.ex:4307: Enum.map/2
    (ash_graphql 0.22.11) lib/resource/resource.ex:1638: AshGraphql.Resource.manage_pkey_fields/4
    (ash_graphql 0.22.11) lib/resource/resource.ex:1319: AshGraphql.Resource.managed_relationship_input/6
    (elixir 1.14.3) lib/enum.ex:1658: Enum."-map/2-lists^map/1-0-"/2
    (elixir 1.14.3) lib/enum.ex:4249: Enum.flat_map_list/2
    (elixir 1.14.3) lib/enum.ex:4250: Enum.flat_map_list/2

ZachDaniel:

Yeah, I was meaning you wouldn’t need the additional options

moxley:

Do you mean these options:

on_lookup: :relate,
on_no_match: :create

ZachDaniel:

yes

ZachDaniel:

Is that stacktrace from the latest ash_graphql release?

moxley:

No. I’ll update it and report back.

ZachDaniel:

awesome, thanks 😄

moxley:

I just upgraded to ash_graphql 0.23.0, ash 2.6.26, and ash_postgres 1.3.17. So far I’m getting this:

mix test test/gf_web/graphql/absinthe_schema/web_component_test.exs:153
Compiling 117 files (.ex)

== Compilation error in file lib/gf_web/graphql/absinthe_schema.ex ==
** (Absinthe.Schema.Error) Compilation failed:
---------------------------------------
## Locations
/Users/mstratto/work/moxley/gf-core/deps/ash_graphql/lib/resource/resource.ex:3083

In Web_site, nil is not defined in your schema.

Types must exist if referenced.
---------------------------------------
## Locations
/Users/mstratto/work/moxley/gf-core/deps/ash_graphql/lib/resource/resource.ex:2246

In Web_site, :_filter_input is not defined in your schema.

Types must exist if referenced.


    (absinthe 1.7.0) lib/absinthe/schema.ex:366: Absinthe.Schema.__after_compile__/2
    (stdlib 4.2) lists.erl:1350: :lists.foldl/3

ZachDaniel:

🤔 do you have any resources that have the AshGraphql.Resource extension but not a type :type configuration?

moxley:

Um, I’m not sure. I don’t know what a AshGraphql.Resource extension is.

ZachDaniel:

when you add it to your resource you do extensions: [AshGraphql.Resource] in the resource

ZachDaniel:

So we’re looking for resources where you’ve done that but not defined a type

moxley:

Oh wait. I commented out a bunch of code, and that’s probably what caused that. One moment…

ZachDaniel:

I’m adding a check for that now

ZachDaniel:

(we should be ignoring resources that don’t have a configured type

moxley:

Okay, I’m seeing the new warnings you were mentioning before. I’ll address those first.

ZachDaniel:

You should just be able to add the config, don’t need to worry about step 1 🙂

moxley:

Okay, got through all of that, and no more warnings. The GraphQL response is the same:

[warning] GQL result errors for operation UpdateWebSite: [%{locations: [%{column: 26, line: 2}], message: "Argument \"input\" has invalid value $input.\nIn field \"components\": Expected type \"[UpdateComponentsInput!]\", found [{attrs: [{key: \"class\", value: \"test\"}], id: \"722ea0b1-ad77-452b-ba2d-724cc94c7c9d\", key: \"test-component\", type: \"HTML\", value: \"new value\"}].\nIn element #1: Expected type \"UpdateComponentsInput\", found {attrs: [{key: \"class\", value: \"test\"}], id: \"722ea0b1-ad77-452b-ba2d-724cc94c7c9d\", key: \"test-component\", type: \"HTML\", value: \"new value\"}.\nIn field \"id\": Unknown field."}]

This time, I can add the lookup_with_primary_key?: true option to managed_relationship() , and there’s no compiler error, but the GraphQL response is the same.

moxley:

"In field \"id\": Unknown field."

ZachDaniel:

Okay, so we’re compiling, its just not including the primary key field which you naturally need for the “or update” component of all of this.

ZachDaniel:

lemme take a look

moxley:

Thanks!

ZachDaniel:

fun fact, looks like we just aren’t adding primary key for updates

ZachDaniel:

lemme confirm/try something out

moxley:

Oh! Hmm.

ZachDaniel:

oh, nvm, just getting my brain back in this code

ZachDaniel:

okay, do me a favor and try out the main branch?

moxley:

Yep, one moment…

moxley:

Sorry, I got lost watching the Erlang Movie.

ZachDaniel:

😆 no worries lol

moxley:

It works!

ZachDaniel:

🥳

ZachDaniel:

I’m adding an automated test as well for this case in the future

ZachDaniel:

I’ll cut a release once the test is in

moxley:

Sweet! I’ll add some more tests on my end to verify all the cases for managing the association.

moxley:

The three cases are covered in my tests: items can be added, updated, or deleted.

ZachDaniel:

Just wait til you get to nested managed relationships 😆

ZachDaniel:

okay, made some small tweaks, added some tests and released it.

ZachDaniel:

LMK if the update works for you, when you get a chance 🙂

moxley:

I updated to the new patch version, and my tests are still passing. It’s looking good.

ZachDaniel:

Thanks for bearing with me on that 😅

moxley:

Thanks for being quick to respond!