opened
spawnia
username_0
**Related Issue(s)**

#131

**PR Type**

Feature/RFC

**Changes**

This PR exists exists as a simple first attempt of implementing nested mutations. I would like to gather some opinions if the direction for this is right and then iterate on that.

@hailwood @hosmelq @username_2 @username_1 @4levels @lucasmichot @kikoseijo @Zn4rK @alexwhb @darrenmerrett @username_3 

I used some ideas from https://www.prisma.io/docs/reference/prisma-api/mutations-ol0yuoz6go/#nested-mutations and they way they are doing nested mutations, and tried to combine that with the way Eloquent models work.

**Example Schema**

````graphql
type User {
  id: ID!
  email: String!
  posts: [Post!] @hasMany
}

type Post {
  id: ID!
  title: String!
  author: User! @belongsTo
}

type Mutation {
  createUser(input: CreateUserInput!): User
  createPost(input: CreatePostInput!): Post
  updateUser(input: UpdateUserInput!): User
  updatePost(input: UpdatePostInput): Post
  deleteUser(id: ID!): User
  deletePost(id: ID!): Post
}

input CreateUserInput {
  email: String!
  posts: CreatePostRelation
}

input CreatePostRelation {
  create: [CreatePostInput!]
}

input CreatePostInput {
  title: String!
  author: ID
}

input UpdateUserInput {
  id: ID
  email: String
  posts: UpdatePostRelation
}

input UpdatePostRelation{
  create: [CreatePostInput!]
  update: [UpdatePostInput!]
  delete: [ID!]
}

input UpdatePostInput {
  id: ID
  title: String!
  author: ID
}
````

**Example Create**

````graphql
mutation {
  createUser(
    input: {
      email: "adf@asdf.de"
      posts: {
        create: [
          { title: "post a" }
          { title: "post b" }
        ]
      }
    }
  ) {
    id
  }
}
````

**Example Update**

````graphql
mutation {
  updateUser(
    input: {
	  id: 2
      email: "adf@asdf.de"
      posts: {
        create: [
          { title: "post c" }
        ]
        update: [
          { id: 2, title: "post cysadf" }
        ]
        delete: [ 23, 4 ]
      }
    }
  ) {
    id
  }
}
````
[RFC] Nested mutations prototype/discussion
issue
created
olivernybroe
username_1
I like the concept a lot. This is really something I would find useful, and I actually think this should belong in Lighthouse, not as a package for itself.

How much of it would we be able to autogenerate? Can you add a schema example for the generated and non generated?
comment
created
spawnia
username_0
@username_1 Right now for my personal use case i auto-generated the whole schema from a database schema, which i also used to generate my models.

I like the approach that @username_2 outlined [here](https://github.com/nuwave/lighthouse/issues/131#issuecomment-392208560), where the only required schema definition is the Object Type, and all the Queries, Mutations and InputObjectTypes are then generated from that.

As a first step, i would try to form the basic building blocks of nested mutations, and then build further abstraction on top.
comment
created
chrissm79
username_2
@username_0 Looks like a great start, pretty excited about this one!!! Quick question, are you planning on using a Schema Manipulator directive (i.e., using a new `@crud` directive or adding a boolean argument to the `@model` directive) that would be defined on the `User` and `Post` types?
comment
created
spawnia
username_0
Tbh i am not all that happy with how `@model` does automatically make the type a relay node. I would like to move that to a more specific directive name in the future, and couple the `@model` directive just with Eloquent functionality.

The `@crud` directive might be a nice way to go regardless, since it pretty clearly indicates a lot of magic happening. Let's see how configurable we need it to be, i would go with an opionionated approach at first and if use cases pop up possibly expand from there.
comment
created
maarten00
username_3
This would be great. I was looking to implement exactly this schema into my own project, but ran into the same issue as described in #208. This allows for much cleaner syntax in mutations and potentially code reuse, especially for complex, multi-model mutations.
comment
created
spawnia
username_0
So, after discussing this with @username_1 my plan is to include this as an experimental feature of v2.2

We can kill two birds with one stone and solve #233 as well. I plan to add an argument to the create directive, like `@create(flatten: true)` which unpacks the first argument and uses that to create the model.

How about transactions? I was thinking to always wrap the whole mutation in a transaction, since a failure in one of the nested mutations should cause the mutation to fail atomically.
comment
created
maarten00
username_3
232|         }
    233|     }
    234|
    235|     /**

  Exception trace:

  1   GraphQL\Utils\Utils::invariant("createClubInput.name field type has a resolve property, but Input Types cannot define resolvers.")
      /Users/maarten/workspace/projectname/vendor/webonyx/graphql-php/src/Type/Definition/InputObjectType.php:127

  2   GraphQL\Type\Definition\InputObjectType::assertValid()
      /Users/maarten/workspace/projectname/vendor/webonyx/graphql-php/src/Type/Schema.php:423

  3   GraphQL\Type\Schema::assertValid()
      /Users/maarten/workspace/projectname/vendor/nuwave/lighthouse/src/Console/ValidateSchemaCommand.php:30

  4   Nuwave\Lighthouse\Console\ValidateSchemaCommand::handle()
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29

  5   call_user_func_array([])
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:29

  6   Illuminate\Container\BoundMethod::Illuminate\Container\{closure}()
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:87

  7   Illuminate\Container\BoundMethod::callBoundMethod(Object(Illuminate\Foundation\Application), Object(Closure))
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Container/BoundMethod.php:31

  8   Illuminate\Container\BoundMethod::call(Object(Illuminate\Foundation\Application), [])
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Container/Container.php:564

  9   Illuminate\Container\Container::call()
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Console/Command.php:184

  10  Illuminate\Console\Command::execute(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))
      /Users/maarten/workspace/projectname/vendor/symfony/console/Command/Command.php:251

  11  Symfony\Component\Console\Command\Command::run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Illuminate\Console\OutputStyle))
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Console/Command.php:171

  12  Illuminate\Console\Command::run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
      /Users/maarten/workspace/projectname/vendor/symfony/console/Application.php:886

  13  Symfony\Component\Console\Application::doRunCommand(Object(Nuwave\Lighthouse\Console\ValidateSchemaCommand), Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
      /Users/maarten/workspace/projectname/vendor/symfony/console/Application.php:262

  14  Symfony\Component\Console\Application::doRun(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
      /Users/maarten/workspace/projectname/vendor/symfony/console/Application.php:145

  15  Symfony\Component\Console\Application::run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Console/Application.php:89

  16  Illuminate\Console\Application::run(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
      /Users/maarten/workspace/projectname/vendor/laravel/framework/src/Illuminate/Foundation/Console/Kernel.php:122

  17  Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))
      /Users/maarten/workspace/projectname/artisan:37
```
comment
created
spawnia
username_0
@username_3 I ran into this issue before, it is related to how we handle arguments/input objects. As you noticed, it works fine but it prevents validation of the schema, which i find to be an essential feature as part of a CI pipeline.

Could you open a seperate issue for that?

What do you think about my previous comment?
comment
created
maarten00
username_3
Well actually, I ran into the issue when using the code from this PR, so I don't think it should be in a seperate issue, right? Unless you're not running into this problem with your own tests, could you verify that? 

As for your previous comment; 
I'm not sure what the @flatten is supposed to fix/change as opposed to the behavior in your current proposal. Could you explain further? 

I think transactions are a great idea, no issue there.
comment
created
spawnia
username_0
I want to make nested mutation the default and integrate it with the previous `@create` directive. As of now, it expects flat field arguments, and i want to avoid breaking that.
So, the `flatten` flag makes it so that Lighthouse expects a single InputObject instead.
comment
created
spawnia
username_0
It came up in a bunch of other cases, i think it has to do with the missing distinction of InputObjectValues from Fields. So i think it is more closely related to #184 , especially since i have not done anything in regards to how arguments are generally handled in this PR.
comment
created
maarten00
username_3
Ah I see. That sounds like a good idea, but maybe a little bit of flexibility would be nice. In my example above I want to pass the required (relation) ID fields without the input type. Maybe like this: 
```
type Mutation{
    createClub(federation_id: ID!, district_id: ID!, input: createClubInput!): Club @create(model: "Club", flatten: "input")
}
```
Maybe you could even allow passing an array or comma-seperated values to flatten multiple arguments?
comment
created
spawnia
username_0
Seperating out the args like in your example does indeed make flattening way more complicated. It also reduces the reusability of your input type, considering you may use it as a child input of another nested mutation. You can still require the args in the single Input, should that be your concern.
comment
created
maarten00
username_3
True, you could add those there. I do think it should always be possible add more arguments besides the input type itself. So always flattening only the first argument should work just fine.
comment
created
spawnia
username_0
I think with the changes in https://github.com/nuwave/lighthouse/pull/207/commits/7198183d5776a009a853e165d2e41b6bee488703 this is now ready to get merged. @username_2 

Nested mutations are now integrated with the existing `@create` and `@update` directives, but require proper definition of the Input Objects. This does not break existing use cases, but gives users the chance to try this out.

I would mark this as **Experimental** for now, as the very nature of nested mutations requires the setup to be quite opinionated. While this works as is, i do not feel confident in providing a stable API for it quite yet.
comment
created
maarten00
username_3
Ran some more tests with your new code, still works great. Will have to add some explanation to the docs though about use, because of the extra argument in `@create`
comment
created
maarten00
username_3
@username_2 I'm eagerly waiting for this to get merged, is there anything I can do to help? I'd be happy to write documentation or run some extra tests if that speeds up the process.
comment
created
t2thec
username_4
@username_3 Personally I'd love to see some docs. Or at least an example. I've gone through the code and not quite got it yet.

Are we passing `@create(flatten: true)`? not quite sure.

thanks all.
comment
created
chrissm79
username_2
@username_3 @username_0 Apologizes, missed the mention in the comment earlier! Currently reviewing and will try to get this merged in tonight!
comment
created
shazm
username_5
@username_0 Is the above example still valid? I've just tried to use this feature; the parent (User) gets saved but none of the children (posts) do.

Code:

```
type User {
  id: ID!
  email: String!
  posts: [Post!] @hasMany
}

type Post {
  id: ID!
  title: String!
  author: User! @belongsTo
}

type Mutation {
  createUser(input: CreateUserInput!): User
}

input CreateUserInput {
  email: String!
  posts: CreatePostRelation
}

input CreatePostRelation {
  create: [CreatePostInput!]
}

input CreatePostInput {
  title: String!
  author: ID
}
```

And query:

```
mutation {
  createUser(
    input: {
      email: "john@example.com"
      posts: {
        create: [
          { title: "First Post!" }
        ]
      }
    }
  ) {
    email
  }
}
```
comment
created
spawnia
username_0
Hey @username_5 thanks for getting involved.

The main thing missing from your schema was the `@create` directive. Apart from that, it does look fine. Added a few `!` where i think they are appropriate.

```graphql
type User {
  id: ID!
  email: String!
  posts: [Post!]! @hasMany
}

type Post {
  id: ID!
  title: String!
  user: User! @belongsTo
}

type Mutation {
  createUser(input: CreateUserInput!): User!
}

input CreateUserInput {
  email: String!
  posts: CreatePostRelation
}

input CreatePostRelation {
  create: [CreatePostInput!]!
}

input CreatePostInput {
  title: String!
}
```

Does your `User` model have a relationship method `posts` that returns an `instanceof HasMany`?
comment
created
shazm
username_5
Awesome that was it; just missing the `@create(flatten: true)`. I'll send through a pull request a bit later for adding this to the docs; it's super handy! Thanks @username_0!
comment
created
ouily
username_6
Hello,

I just tested but it doesn't work with **belongsToMany** directive: 


```
type Post {
    id: ID!
    subject: String!
    text: String!
    created_at: DateTime!
    updated_at: DateTime!
    tags: [Tag!] @belongsToMany
}

type Tag {
    id: ID!
    name: String!
    slug: String!
    created_at: DateTime!
    updated_at: DateTime!
    posts: [Post!] @belongsToMany
}

type Query {
    post(id: ID @eq): Post @find(model: "App\\Post")
}

type Mutation { 
    createPost(input: CreatePostInput!): Post @create(model: "App\\Post", flatten: true)
}

input CreatePostInput {
  user_id: ID!
  topic_id: ID!
  subject: String!
  text: String!
  tags: CreateTagRelation
}

input CreateTagRelation {
  create: [CreateTagInput!]!
}

input CreateTagInput {
  name: String!
  slug: String!
}

```
:(
comment
created
sadnub
username_7
Do I need the ID scalar and the specifying the model in the mutations? I thought that was only for Relay but I could be mistaken
comment