amplify-swift icon indicating copy to clipboard operation
amplify-swift copied to clipboard

Add control to which GraphQL connections of a model are loaded when constructing a query with Amplify.API.query

Open spenceps opened this issue 5 years ago • 0 comments

Is your feature request related to a problem? Please describe. The document generated with a GraphQL request makes some assumptions about which connections are loaded. It would be very helpful to make a way to indicate which connections should be added to the graphQL document.

Right now the assumption is that a connection only included if it is the connection owner and it is a required field (! in GraphQL)

ModelSchema+GraphQL.swift - if it has an association it has to be the owner to be included

var graphQLFields: [ModelField] {
    sortedFields.filter { field in
        !field.hasAssociation || field.isAssociationOwner
    }
}

SelectionSet.swift - if it is not a required field then it isn't included in the section set

func withModelFields(_ fields: [ModelField]) {
    fields.forEach { field in
        let isRequiredAssociation = field.isRequired && field.isAssociationOwner
        if isRequiredAssociation, let associatedModel = field.associatedModel {
            let child = SelectionSet(value: .init(name: field.name, fieldType: .model))
            child.withModelFields(associatedModel.schema.graphQLFields)
            self.addChild(settingParentOf: child)
        } else {
            self.addChild(settingParentOf: .init(value: .init(name: field.graphQLName, fieldType: .value)))
        }
    }

    addChild(settingParentOf: .init(value: .init(name: "__typename", fieldType: .value)))
}

Describe the solution you'd like Passing fields that need to be loaded when constructing a GraphQLRequest

public static func list<M: Model>(_ modelType: M.Type,
                                      where predicate: QueryPredicate? = nil,
                                      include connections: [String]) -> GraphQLRequest<[M]>

connections would come from ModelInstance.keys.theKey

Describe alternatives you've considered

  • Changing the schema is the easiest way around this in this situation but may not be the best solution
  • There could be a mode when creating a query that has a level of connections to load
  • I'm sure there are a lot of other ways to accomplish this and hope that better solutions are presented

Additional context The following is a sample schema. When making a query for ChatRoomMembers the user is a part of the chatRoom connected object isn't loaded because it isn't required.

type User @model
{
  id: ID!
  email: String!
  chatRooms: [ChatRoomMember] @connection(keyName: "byMember", fields: ["id"])
}

# Many-to-many join between ChatRooms and Users
type ChatRoomMember @model
@key(name: "byChatRoom", fields: ["chatRoomID", "userID"], queryField: "listMembersByChatRoom")
@key(name: "byMember", fields: ["userID", "chatRoomID"], queryField: "listChatRoomByMember")
{
  id: ID!

  userID: ID!
  user: User @connection(fields: ["userID"])
  chatRoomID: ID!
  chatRoom: ChatRoom @connection(fields: ["chatRoomID"])
}

type ChatRoom @model
{
  id: ID!
  createdAt: String
  updatedAt: String

  name: String!

  members: [ChatRoomMember] @connection(keyName: "byChatRoom", fields: ["id"])

  creatorUserID: ID!
  creator: User @connection(fields: ["creatorUserID"])
}

Query Code

let predicate = QueryPredicateOperation(field: "userID", operator: .equals(user.id))
let request = GraphQLRequest<ChatRoomMember>.list(ChatRoomMember.self, where: predicate)
_ = Amplify.API.query(request: request, listener: { (event) in
    switch event {
    case .success(let result):
        switch result {
        case .success(let chatroomMembers):
            let chatRooms = chatroomMembers.compactMap { $0.chatRoom }
//no chatrooms because it is always nil
        case .failure(let error):
            print("got error getting ChatRoom Members \(error)")
        }
    case .failure(let error):
        print("got error getting ChatRoom Members event \(error)")
    }
})

Make ChatRoom Member code:

let chatroom = ChatRoom(name: title, creator: currentUser)
_ = Amplify.API.mutate(request: .create(chatroom)) { (event) in
    switch event {
    case .success(let result):
        switch result {
            case .success(let newChatRoom):
                let chatRoomMember = ChatRoomMember(user: currentUser, chatRoom: newChatRoom)
                _ = Amplify.API.mutate(request: .create(chatRoomMember), listener: { (memberEvent) in
                    switch memberEvent {
                    case .success(let memberResult):
                        switch memberResult {
                        case .success(let chatRoomMember):
                            print("saved member \(chatRoomMember)")
                        case .failure(let error):
                            print("got error \(error)")
                        }
                        
                    case .failure(let error):
                        print("got error \(error)")
                    }
                })
            case .failure(let error):
                print("failure: \(error)")
        }
    case .failure(let error):
        print("error \(error)")
    }
}

spenceps avatar Jun 04 '20 17:06 spenceps