Update, create and delete hasMany() relations in one go

Published 9 months ago by davestewart

Is it possible to pass an array to Eloquent and have it update, create or delete relations in one shot?

My usage: a SPA and a user has many addresses:

  • let's say he has addresses 1 and 2, and these are downloaded to the app.
  • in the app, the user deletes 1, amends 2, and adds a 3rd.
  • we now have addresses 2 and 3

When I upload to the server, I want to do a diff on existing addresses, and take action accordingly:

  • delete address 1
  • update address 2
  • create address 3

Is there a built-in way to do this?

The other approach I suppose would be to do this one at a time, but in my case, I want the user to be able to update various forms in sequence, then only at the end submit data.

So ideally I'd have something like:

$user->updateRelations($arr)

But happy to do it all manually.

Cheers, Dave

Best Answer (As Selected By davestewart)
davestewart

I guess it's a manual job then:

function crudPartition($oldData, $newData)
{
    // ids
    $oldIds = array_pluck($oldData, 'id');
    $newIds = array_filter(array_pluck($newData, 'id'), 'is_numeric');

    // groups
    $delete = collect($oldData)
        ->filter(function ($model) use ($newIds) {
            return !in_array($model->id, $newIds);
        });

    $update = collect($newData)
        ->filter(function ($model) use ($oldIds) {
            return property_exists($model, 'id') && in_array($model->id, $oldIds);
        });

    $create = collect($newData)
        ->filter(function ($model) {
            return !property_exists($model, 'id');
        });

    // return
    return compact('delete', 'update', 'create');
}

// data
$oldData = json_decode('[{"id":1,"name":"one"},{"id":2,"name":"two"}]');
$newData = json_decode('[{"id":2,"name":"TWO"},{"name":"three"}]');

// results
$results = crudPartition($oldData, $newData);
print_r($results);

// do something
$results['create']->each( ... );
Array
(
    [delete] => Array
        (
            [0] => stdClass Object
                (
                    [id] => 1
                    [name] => one
                )

        )

    [update] => Array
        (
            [0] => stdClass Object
                (
                    [id] => 2
                    [name] => TWO
                )

        )

    [create] => Array
        (
            [1] => stdClass Object
                (
                    [name] => three
                )

        )

)
SyedAbuthahir

You have an option for creating or updating resource in single method look at this Inserting & Updating Models section in laravel official document section.

davestewart

I guess it's a manual job then:

function crudPartition($oldData, $newData)
{
    // ids
    $oldIds = array_pluck($oldData, 'id');
    $newIds = array_filter(array_pluck($newData, 'id'), 'is_numeric');

    // groups
    $delete = collect($oldData)
        ->filter(function ($model) use ($newIds) {
            return !in_array($model->id, $newIds);
        });

    $update = collect($newData)
        ->filter(function ($model) use ($oldIds) {
            return property_exists($model, 'id') && in_array($model->id, $oldIds);
        });

    $create = collect($newData)
        ->filter(function ($model) {
            return !property_exists($model, 'id');
        });

    // return
    return compact('delete', 'update', 'create');
}

// data
$oldData = json_decode('[{"id":1,"name":"one"},{"id":2,"name":"two"}]');
$newData = json_decode('[{"id":2,"name":"TWO"},{"name":"three"}]');

// results
$results = crudPartition($oldData, $newData);
print_r($results);

// do something
$results['create']->each( ... );
Array
(
    [delete] => Array
        (
            [0] => stdClass Object
                (
                    [id] => 1
                    [name] => one
                )

        )

    [update] => Array
        (
            [0] => stdClass Object
                (
                    [id] => 2
                    [name] => TWO
                )

        )

    [create] => Array
        (
            [1] => stdClass Object
                (
                    [name] => three
                )

        )

)
Snapey
Snapey
9 months ago (1,003,075 XP)

yep, manual

davestewart

Thanks for confirming @Snapey.

Hopefully this post will be useful to someone else anyway.

Ranx99

was looking for a way to do that, thanks @davestewart

fabiomezini

Im a bit late, but maybe it could help someone.

I did a litle implementation


namespace App\Services;

use App\Http\Requests\Person\StoreRequest;
use App\Http\Requests\Person\UpdateRequest;
use App\Models\Person;
use DB;
use Illuminate\Database\Eloquent\Relations\Relation;

class PersonService
{

    //... some other code here

    public function update(Person $person, UpdateRequest $request)
    {
        try {           
            DB::beginTransaction();

            $data = $request->validated();

            $person->fill($data['person'])->save();

            $this->proccesRelationWithRequest(
                $person->addresses(),
                $data['addresses']
            );

            $this->proccesRelationWithRequest(
                $person->contacts(),
                $data['contacts']
            );
            
            DB::commit();
            return $person;
        } catch (\Exception $e) {
            DB::rollback();
            throw $e;
        }
    }

    protected function proccesRelationWithRequest(Relation $relation, array $items)
    {
        $relation->getResults()->each(function($model) use ($items) {
            foreach ($items as $item) {
                if ($model->uuid === ($item['uuid'] ?? null)) {
                    $model->fill($item)->save();
                    return;
                }
            }

            return $model->delete();
        });

        foreach ($items as $item) {
            if (!isset($item['uuid']))
                $relation->create($item);
        };
    }

    //...some more code here
}

Please sign in or create an account to participate in this conversation.