mongodb / laravel-mongodb

A MongoDB based Eloquent model and Query builder for Laravel (Moloquent)
https://www.mongodb.com/docs/drivers/php/laravel-mongodb/
MIT License
7.01k stars 1.43k forks source link

EmbedsOne Relationship do not save / create / update in db #2398

Closed beeards closed 11 months ago

beeards commented 2 years ago
"jenssegers/mongodb": "^3.9",
"laravel/lumen-framework": "^9.0"
"php": "^8.0",
"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' => 'supplier@gmail.com',
        // ! '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: ```txt • 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") ```
SanaviaNicolas commented 2 years ago

Hi @beeards, you can try this package!

beeards commented 2 years ago

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.

SanaviaNicolas commented 2 years ago

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.

beeards commented 2 years ago

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.

hans-thomas commented 11 months ago

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