laravel-mongodb icon indicating copy to clipboard operation
laravel-mongodb copied to clipboard

EmbedsOne Relationship do not save / create / update in db

Open beeards opened this issue 2 years ago • 4 comments

  • Laravel-mongodb Version:
"jenssegers/mongodb": "^3.9",
"laravel/lumen-framework": "^9.0"
  • PHP Version:
"php": "^8.0",
  • Database Driver & Version:
"mongodb/mongodb": "^1.12.0",
MongoDB shell version 4.4.13
libmongoc bundled version 1.21.1
libmongocrypt bundled version 1.3.2
mongodb-community version 4.4

Description:

EmbedsOne Relationship do not save / create / update in db. Tested all the methods in README.md #embedsone-relationship or have I missed something.

Steps to reproduce

# @ database/migrations/create_suppliers_collection.php

use Illuminate\Database\Migrations\Migration;
use Jenssegers\Mongodb\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    protected $connection = 'mongodb';

    public function up()
    {
        Schema::create('suppliers', function (Blueprint $collection) {
            $collection->unique(['email']);
        });
    }

    public function down()
    {
        Schema::dropIfExists('suppliers', function (Blueprint $collection) {
            $collection->dropIndex(['email']);
        });
    }
};

# @ app/Models/Supplier.php
declare(strict_types=1);

namespace App\Models;

use Jenssegers\Mongodb\Eloquent\Model;
use Jenssegers\Mongodb\Relations\EmbedsOne;

class Supplier extends Model
{
    protected $fillable = [
        'address',
        'email',
    ];

    public function addresses(): EmbedsOne
    {
        return $this->embedsOne(SupplierAddress::class);
    }
}
# @ app/Models/SupplierAddress.php
declare(strict_types=1);

namespace App\Models;

use Jenssegers\Mongodb\Eloquent\Model;

class SupplierAddress extends Model
{
    protected $fillable = [
        'streetAddress',
    ];
}
# @ tests/SeederTest.php
declare(strict_types=1);

namespace Tests;

use App\Models\Model;
use App\Models\Supplier;
use App\Models\SupplierAddress;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;

class SeederTest extends TestCase
{
    protected $data_source = [
        'email' => '[email protected]',
        // ! 'address' => 'street, 1',
        'address' => [
            'streetAddress' => 'street, 1',
            'dirt' => 'do not save on mongodb',
        ],
        'dirt' => 'do not save on mongodb',
    ];

    public function test_that_inserts_and_updates_an_embeds_one()
    {

        DB::collection('suppliers')->delete();


        # for supplier prepare only fillable fields & fill without address
        $new_supplier = new Supplier();
        $data_s_fillable = $new_supplier->getFillable();
        if (count($data_s_fillable) > 0) {
            $data_s_clean = array_intersect_key($this->data_source, array_flip($data_s_fillable));
        }
        $new_supplier->fill(
            Arr::except($data_s_clean, ['address'])
        );

        # create supplier without address (clean)
        $supplier = Supplier::create($new_supplier->toArray());


        # for supplier->address prepare only fillable fields & fill
        $new_supplier_address = new SupplierAddress();
        $data_a_fillable = $new_supplier_address->getFillable();
        if (count($data_a_fillable) > 0) {
            $data_a_clean = array_intersect_key($this->data_source['address'], array_flip($data_a_fillable));
        }
        $new_supplier_address->fill(
            $data_a_clean
        );

        /**
         * Save supplier->address (clean)
         * @see https://github.com/jenssegers/laravel-mongodb#embedsone-relationship
         * @example
         * $author = $book->author()->save(
         *     new Author(['name' => 'John Doe'])
         * );
         */
        $supplier->address()->save($new_supplier_address->toArray());

        # get supplier from db
        $stored_supplier = Supplier::first();

        $this->assertEquals(
            $this->data_source['address']['streetAddress'],
            $stored_supplier->address()->streetAddress
        );
    }

    public function test_that_inserts_and_updates_similiar_an_embeds_one()
    {

        DB::collection('suppliers')->delete();


        # for supplier prepare only fillable fields & fill without address
        $new_supplier = new Supplier();
        $data_s_fillable = $new_supplier->getFillable();
        if (count($data_s_fillable) > 0) {
            $data_s_clean = array_intersect_key($this->data_source, array_flip($data_s_fillable));
        }
        $new_supplier->fill(
            Arr::except($data_s_clean, ['address'])
        );

        # create supplier without address (clean)
        $supplier = Supplier::create($new_supplier->toArray());


        # for supplier->address prepare only fillable fields & fill
        $new_supplier_address = new SupplierAddress();
        $data_a_fillable = $new_supplier_address->getFillable();
        if (count($data_a_fillable) > 0) {
            $data_a_clean = array_intersect_key($this->data_source['address'], array_flip($data_a_fillable));
        }
        $new_supplier_address->fill(
            $data_a_clean
        );

        /**
         * Save supplier->address similar (clean)
         * @see https://github.com/jenssegers/laravel-mongodb#embedsone-relationship
         * @example
         * $author =
         *     $book->author()
         *          ->create(['name' => 'John Doe']);
         */
        $supplier->address()->create($new_supplier_address->toArray());

        # get supplier from db
        $stored_supplier = Supplier::first();

        $this->assertEquals(
            $this->data_source['address']['streetAddress'],
            $stored_supplier->address()->streetAddress
        );
    }

    public function test_that_updates_an_embeds_one()
    {

        DB::collection('suppliers')->delete();


        # for supplier prepare only fillable fields & fill without address
        $new_supplier = new Supplier();
        $data_s_fillable = $new_supplier->getFillable();
        if (count($data_s_fillable) > 0) {
            $data_s_clean = array_intersect_key($this->data_source, array_flip($data_s_fillable));
        }
        $new_supplier->fill(
            Arr::except($data_s_clean, ['address'])
        );

        # create supplier without address (clean)
        $supplier = Supplier::create($new_supplier->toArray());


        # for supplier->address prepare only fillable fields & fill
        $new_supplier_address = new SupplierAddress();
        $data_a_fillable = $new_supplier_address->getFillable();
        if (count($data_a_fillable) > 0) {
            $data_a_clean = array_intersect_key($this->data_source['address'], array_flip($data_a_fillable));
        }
        $new_supplier_address->fill(
            $data_a_clean
        );

        /**
         * Save supplier->address similar (clean)
         * @see https://github.com/jenssegers/laravel-mongodb#embedsone-relationship
         * @example
         * $author = $book->author;
         *
         * $author->name = 'Jane Doe';
         * $author->save();
         */
        $supplier_address = $supplier->address;
        $supplier_address->streetAddress = $new_supplier_address->toArray()['streetAddress'];
        $supplier_address->save();

        $this->assertEquals(
            $this->data_source['address']['streetAddress'],
            $supplier->address()->streetAddress
        );
    }
}


Expected behaviour

Save in db and get embebed one data.

   PASS  Tests\SeederTest
  ✓ that inserts and updates an embeds one
  ✓ that inserts and updates similar an embeds one
  ✓ that updates an embeds one

Actual behaviour

Do not save in db and can't get embebed one data.

   FAIL  Tests\SeederTest
  ⨯ that inserts and updates an embeds one
  ⨯ that inserts and updates similar an embeds one
  ⨯ that updates an embeds one
Logs:

  • Tests\SeederTest > that inserts and updates an embeds one
  Failed asserting that null matches expected 'street, 1'.

  at tests/SeederTest.php:67
     63▕         $stored_supplier = Supplier::first();
     64▕
     65▕         $this->assertEquals(
     66▕             $data_source['address']['streetAddress'],
  ➜  67▕             $stored_supplier->address()->streetAddress
     68▕         );
     69▕     }
     70▕
     71▕     public function test_that_inserts_and_updates_similiar_an_embeds_one()

  • Tests\SeederTest > that inserts and updates similiar an embeds one
   MongoDB\Driver\Exception\BulkWriteException

  E11000 duplicate key error collection: foo.suppliers index: name_1 dup key: { name: null }

  at vendor/mongodb/mongodb/src/Operation/InsertOne.php:123
    119▕
    120▕         $bulk = new Bulk($this->createBulkWriteOptions());
    121▕         $insertedId = $bulk->insert($this->document);
    122▕
  ➜ 123▕         $writeResult = $server->executeBulkWrite($this->databaseName . '.' . $this->collectionName, $bulk, $this->createExecuteOptions());
    124▕
    125▕         return new InsertOneResult($writeResult, $insertedId);
    126▕     }
    127▕

      +16 vendor frames
  17  tests/SeederTest.php:118
      Jenssegers\Mongodb\Eloquent\Model::__call("create")

  • Tests\SeederTest > that updates an embeds one
   LogicException

  App\Models\Supplier::address must return a relationship instance.

  at vendor/illuminate/database/Eloquent/Concerns/HasAttributes.php:549
    545▕                     '%s::%s must return a relationship instance, but "null" was returned. Was the "return" keyword used?', static::class, $method
    546▕                 ));
    547▕             }
    548▕
  ➜ 549▕             throw new LogicException(sprintf(
    550▕                 '%s::%s must return a relationship instance.', static::class, $method
    551▕             ));
    552▕         }
    553▕

      +3 vendor frames
  4   tests/SeederTest.php:177
      Illuminate\Database\Eloquent\Model::__get("address")

beeards avatar Jun 03 '22 09:06 beeards

Hi @beeards, you can try this package!

SanaviaNicolas avatar Jun 03 '22 12:06 SanaviaNicolas

Hi @SanaviaNicolas, your package looks good!

But I'm trying to update embedded models with jenssegers/laravel-mongodb package. It's supposed to uses exactly the same methods as original Laravel classes.

beeards avatar Jun 05 '22 18:06 beeards

Hi @beeards, this package has some bugs on embeds relationships and they no longer be maintained. Laravel Mongo Auto Sync provides a better support for MongoDB relationships and all the methods are based on eloquent methods. Check our documentation.

SanaviaNicolas avatar Jun 06 '22 13:06 SanaviaNicolas

Hi @SanaviaNicolas! I just migrated all to mysql, because I found out, and it seems that it will not be maintained (in short-mid term? 🤷), as you said.

* I've been reading the documentation and reviewing the code of Laravel Mongo Auto Sync and looks good, it has a lot of work behind, I will keep it in mind for other projects. IMO maybe some files need a revision (e.g.: Traits/ModelAdditionalMethod.php), I guess it's because it's being used in other projects.

beeards avatar Jun 06 '22 23:06 beeards

@GromNaN According to the tests, it is fixed now.

hans-thomas avatar Nov 19 '23 08:11 hans-thomas