Skip to content

Commit

Permalink
Refactor Create/Update/Upsert directives to DRY
Browse files Browse the repository at this point in the history
  • Loading branch information
Jose Manuel Cardona committed Oct 13, 2019
1 parent b48d1a6 commit 87eec45
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 205 deletions.
50 changes: 50 additions & 0 deletions docs/master/eloquent/getting-started.md
Expand Up @@ -218,6 +218,56 @@ may fail to find the model you provided and return `null`:
}
```

## Upsert

You can create or update a model with the [@upsert](../api-reference/directives.md#upsert) directive.

```graphql
type Mutation {
upsertUser(id: ID!, name: String!, email: String): User @upsert
}
```

Since upsert can create or update your data you must have all the minimum fields for a creation as required.
For example the `id` is going to be always mandatory.

```graphql
mutation {
upsertUser(id: "123" name: "Hillary"){
id
name
email
}
}
```

```json
{
"data": {
"upsertUser": {
"id": "123",
"name": "Hillary",
"email": null
}
}
}
```

Due to upsert does a create or update, it produces an idempotent result. This means that you can execute it

This comment has been minimized.

Copy link
@spawnia

spawnia Oct 13, 2019

Collaborator

That is not necessarily true, as timestamps may be updated.

I think we can just skip that section, the docs are clear regardless.

This comment has been minimized.

Copy link
@joskfg

joskfg Oct 13, 2019

Collaborator

Actually a lot of things can be updated because you can implement logic in setters, I remove this two lines.

as many times as you want, that it will produce always the same result.

```json
{
"data": {
"upsertUser": {
"id": "123",
"name": "Hillary",
"email": null
}
}
}
```

## Delete

Deleting models is a breeze using the [@delete](../api-reference/directives.md#delete) directive. Dangerously easy.
Expand Down
100 changes: 96 additions & 4 deletions docs/master/eloquent/nested-mutations.md
Expand Up @@ -66,13 +66,15 @@ input CreateAuthorRelation {
connect: ID
create: CreateUserInput
update: UpdateUserInput
upsert: UpsertUserInput
}
```

There are 3 possible operations that you can expose on a `BelongsTo` relationship when creating:

This comment has been minimized.

Copy link
@spawnia

spawnia Oct 13, 2019

Collaborator

You can expose the following operations on a BelongsTo relationship when creating:

- `connect` it to an existing model
- `create` a new related model and attach it
- `update` an existing model and attach it
- `upsert` a new or an existing model and attach it

Finally, you need to define the input that allows you to create a new `User`.

Expand Down Expand Up @@ -160,6 +162,7 @@ type Mutation {
}

input UpdatePostInput {
id: ID!

This comment has been minimized.

Copy link
@spawnia

spawnia Oct 13, 2019

Collaborator

Great catch!

title: String
author: UpdateAuthorRelation
}
Expand All @@ -180,6 +183,7 @@ and allows the query string to be mostly static, taking a variable value to cont
```graphql
mutation UpdatePost($disconnectAuthor: Boolean){
updatePost(input: {
id: 1
title: "An updated title"
author: {
disconnect: $disconnectAuthor
Expand All @@ -200,13 +204,47 @@ The `author` relationship will only be disconnected if the value of the variable
{
"data": {
"updatePost": {
"id": 1,
"title": "An updated title",
"author": null
}
}
}
```

When issuing an upsert you will have all the update options, so the behaviour is the same. In the case that
you create a new model it will have the same behaviour than an update that doesn't have any connected model.

```graphql
mutation UpdatePost($disconnectAuthor: Boolean){
upsertPost(input: {
id: 1
title: "An updated or created title"
author: {
disconnect: $disconnectAuthor
}
}){
id
title
author {
name
}
}
}
```

```json
{
"data": {
"upsertPost": {
"id": 1,
"title": "An updated or created title",
"author": null
}
}
}
```

## Has Many

The counterpart to a `BelongsTo` relationship is `HasMany`. We will start
Expand Down Expand Up @@ -304,6 +342,7 @@ input UpdateUserInput {
input UpdatePostsRelation {
create: [CreatePostInput!]
update: [UpdatePostInput!]
upsert: [UpsertPostInput!]
delete: [ID!]
}

Expand All @@ -315,6 +354,11 @@ input UpdatePostInput {
id: ID!
title: String
}

input UpsertPostInput {
id: ID!
title: String
}
```

```graphql
Expand Down Expand Up @@ -347,6 +391,9 @@ mutation {
}
```

The behaviour when you are upserting is a mix between updating and creating and it won't fail
if the model exists or not because it will produce the needed action (update or create) in every case.

## Belongs To Many

A belongs to many relation allows you to create new related models as well
Expand All @@ -364,13 +411,19 @@ input CreatePostInput {

input CreateAuthorRelation {
create: [CreateAuthorInput!]
upsert: [UpsertAuthorInput!]
connect: [ID!]
sync: [ID!]
}

input CreateAuthorInput {
name: String!
}

input UpsertAuthorInput {
id: ID!
name: String!
}
```

Just pass the ID of the models you want to associate or their full information
Expand All @@ -386,6 +439,12 @@ mutation {
name: "Herbert"
}
]
upsert: [
{
id: 2000
name: "Newton"
}
]
connect: [
123
]
Expand All @@ -399,7 +458,7 @@ mutation {
}
```

Lighthouse will detect the relationship and attach/create it.
Lighthouse will detect the relationship and attach/update/create it.

```json
{
Expand All @@ -411,6 +470,10 @@ Lighthouse will detect the relationship and attach/create it.
"id": 165,
"name": "Herbert"
},
{
"id": 2000,
"name": "Newton"
},
{
"id": 123,
"name": "Franz"
Expand Down Expand Up @@ -442,7 +505,7 @@ mutation {
}
```

Updates on BelongsToMany relations may expose up to 6 nested operations.
Updates on BelongsToMany relations may expose up to 7 nested operations.

This comment has been minimized.

Copy link
@spawnia

spawnia Oct 13, 2019

Collaborator

Lets reframe this so we won't have to keep updating the number

This comment has been minimized.

Copy link
@joskfg

joskfg Oct 13, 2019

Collaborator

Totally agree


```graphql
type Mutation {
Expand All @@ -459,6 +522,7 @@ input UpdateAuthorRelation {
create: [CreateAuthorInput!]
connect: [ID!]
update: [UpdateAuthorInput!]
upsert: [UpsertAuthorInput!]
sync: [ID!]
delete: [ID!]
disconnect: [ID!]
Expand All @@ -472,6 +536,11 @@ input UpdateAuthorInput {
id: ID!
name: String!
}

input UpsertAuthorInput {
id: ID!
name: String!
}
```

## MorphTo
Expand All @@ -495,6 +564,7 @@ type Hour {
type Mutation {
createHour(input: CreateHourInput! @spread): Hour @create
updateHour(input: UpdateHourInput! @spread): Hour @update
upsertHour(input: UpsertHourInput! @spread): Hour @upsert
}

input CreateHourInput {
Expand All @@ -512,6 +582,14 @@ input UpdateHourInput {
hourable: UpdateHourableOperations
}

input UpsertHourInput {
id: ID!
from: String
to: String
weekday: Int
hourable: UpsertHourableOperations
}

input CreateHourableOperations {
connect: ConnectHourableInput
}
Expand All @@ -522,6 +600,12 @@ input UpdateHourableOperations {
delete: Boolean
}

input UpsertHourableOperations {
connect: ConnectHourableInput
disconnect: Boolean
delete: Boolean
}

input ConnectHourableInput {
type: String!
id: ID!
Expand Down Expand Up @@ -575,7 +659,7 @@ The `delete` operation both detaches and deletes the currently associated model.

```graphql
mutation {
updateHour(input: {
upsertHour(input: {
id: 1
weekday: 2
hourable: {
Expand Down Expand Up @@ -608,6 +692,7 @@ input CreateTaskInput {

input CreateTagRelation {
create: [CreateTagInput!]
upsert: [UpsertTagInput!]
sync: [ID!]
connect: [ID!]
}
Expand All @@ -616,6 +701,12 @@ input CreateTagInput {
name: String!
}

input UpsertTagInput {
id: ID!
name: String!
}


type Task {
id: ID!
name: String!
Expand Down Expand Up @@ -649,7 +740,8 @@ mutation {
You can either use `connect` or `sync` during creation.

When you want to create a new tag while creating the task,
you need use the `create` operation to provide an array of `CreateTagInput`:
you need use the `create` operation to provide an array of `CreateTagInput`
or use the `upsert` operation to provide an array of `UpsertTagInput`:

```graphql
mutation {
Expand Down
10 changes: 5 additions & 5 deletions src/Execution/MutationExecutor.php
Expand Up @@ -329,24 +329,24 @@ protected static function handleSingleRelationUpsert(Collection $singleValues, R
/**
* Execute an update mutation.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param \Illuminate\Database\Eloquent\Model $modelInstance
* An empty instance of the model that should be updated
* @param \Illuminate\Support\Collection $args
* The corresponding slice of the input arguments for updating this model
* @param \Illuminate\Database\Eloquent\Relations\Relation|null $parentRelation
* If we are in a nested update, we can use this to associate the new model to its parent
* @return \Illuminate\Database\Eloquent\Model
*/
public static function executeUpdate(Model $model, Collection $args, ?Relation $parentRelation = null): Model
public static function executeUpdate(Model $modelInstance, Collection $args, ?Relation $parentRelation = null): Model
{
$id = $args->pull('id')
?? $args->pull(
$model->getKeyName()
$modelInstance->getKeyName()
);

$model = $model->newQuery()->findOrFail($id);
$modelInstance = $modelInstance->newQuery()->findOrFail($id);

return self::executeUpdateWithLoadedModel($model, $args, $parentRelation);
return self::executeUpdateWithLoadedModel($modelInstance, $args, $parentRelation);
}

/**
Expand Down

0 comments on commit 87eec45

Please sign in to comment.