-
-
Notifications
You must be signed in to change notification settings - Fork 465
Description
Not sure if this is useful for core lighthouse but I can create a PR if it's something of interest
What problem does this feature proposal attempt to solve?
Similar to the eloquent method updateOrCreate
Which possible solutions should be considered?
This is what I currently have in our project, it works in the same way as updateOrCreate with the added feature of being able to restore softDeleted models as well
Usecase for the softdeleted stuff:
We have a proposal which is basically an intermediary table for client/candidate that also uses softdeletes (so it can be restored and we can keep all previous activity around that proposal should we need to look at it) this is restrained to unique([client_id,candidate_id]) so trying to create another row with the same will throw an error, rather than checking if a row exists, and if its deleted, then changing it across multiple api calls, it's easier just to do it through a single directive.
Open to alternative solutions, I kinda threw this together via the other directives/arguments for @create and @update
UpdateOrCreateDirective.php (the actual directive)
class UpdateOrCreateDirective extends MutationExecutorDirective
{
public static function definition(): string
{
return /** @lang GraphQL */ <<<'GRAPHQL'
"""
Attempts to update a record, if not record is found it will create one instead
"""
directive @updateOrCreate(
"""
A list of columns to search for from the list of args
"""
columns: [String!]!
"""
Whether a soft deleted row should be restored and updated
"""
canRestore: Boolean = false
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION
GRAPHQL;
}
protected function makeExecutionFunction(?Relation $parentRelation = null): callable
{
return new UpdateOrCreateModel(
new SaveModel($parentRelation),
$this->columnsArgValue(),
$this->canRestoreArgValue()
);
}
public function columnsArgValue(): array
{
return $this->directiveArgValue('columns');
}
public function canRestoreArgValue(): bool
{
return $this->directiveArgValue('canRestore', false);
}
}UpdateOrCreateModel.php (ArgResolver)
class UpdateOrCreateModel implements ArgResolver
{
/**
* @var callable|ArgResolver
*/
protected $previous;
/**
* @var array
*/
protected $columns;
/**
* @var bool
*/
protected $canRestore;
/**
* @param callable|ArgResolver $previous
*/
public function __construct(callable $previous, array $columns, bool $canRestore = false)
{
$this->previous = $previous;
$this->columns = $columns;
$this->canRestore = $canRestore;
}
/**
* @param Model $model
* @param ArgumentSet $args
*/
public function __invoke($model, $args)
{
/** @var ArgumentSet $belongsTo */
[$belongsTo, $remaining] = ArgPartitioner::relationMethods(
$args,
$model,
BelongsTo::class
);
foreach ($belongsTo->arguments as $relationName => $nestedOperations) {
/** @var BelongsTo $belongsTo */
$belongsTo = $model->{$relationName}();
$belongsToResolver = new ResolveNested(new NestedBelongsTo($belongsTo));
$belongsToResolver($model, $nestedOperations->value);
}
$shouldRestoreSoftDeletes = $this->canRestore && in_array(SoftDeletes::class, class_uses($model), true);
$existingModel = $model
->newQuery()
->when($shouldRestoreSoftDeletes, fn($builder) => $builder->withoutGlobalScope(SoftDeletingScope::class))
->where(
array_intersect_key([
...$remaining->toArray(),
...$model->getAttributes()
], array_flip($this->columns))
)
->first();
if ($shouldRestoreSoftDeletes) {
$args->addValue('deleted_at', null);
}
return ($this->previous)($existingModel ?? $model, $args);
}
}