jenya's avatar
Level 2

Seed belongsTo and hasMany relations together

Hello, Laracasts! I have four models: Card,Document_Card, Person and Document_Person

Relations are:

for Card model

<?php

class Card extends Model  
{
    protected $table ='Cards';
    protected $primaryKey='id';
       
    public function Document_Card()
    {
        return $this->belongsTo('App\Document_Card','Doc_id');
    }

    public function Person()
    {
        return $this->hasMany('App\Person','card_id');
    }
      
 }

for Document_Card

<?php

class Document_Card extends Model 
{   
   public function Card()
    {
        return $this->hasMany('App\Card','Doc_id');
    }
}

for Person model


class Person extends Model  
{
   
    protected $primaryKey='id';
    protected $table='Person';
   
    public function Document_Person()
    {
        return $this->belongsTo('App\Document_Person','doc_id');
    }

    public function Card() //how to seed this relation ????
    {
        return $this->belongsTo('App\Card','card_id');
    }
}

and Document_Person

<?php

class Document_Person extends Model 
{
    public function Person()
    {
        return $this->hasMany('App\Person','doc_id');
    }

}

I make PersonFactory

use Faker\Generator as Faker;

$factory->define(App\Person::class, function (Faker $faker) {
    return [
         .................................
        'work'=>$faker->text($maxNbChars = 90) ,
        'task'=>$faker->text($maxNbChars = 80) ,
        'number'=>$faker->numberBetween($min = 1, $max = 6),
         .................................
        ];
});

Document_PersonFactory

use Faker\Generator as Faker;

$factory->define(App\Document_Person::class, function (Faker $faker) {
    return [
        'Date'=>$faker->dateTimeBetween($startDate = '-2 months', $endDate = '+2 months', $timezone = null),
        'Name'=>$faker->sentence($nbWords = 3, $variableNbWords = true),
    ];
});

and Document_PerosnSeed

use Illuminate\Database\Seeder;

class Documents_IspsSeed extends Seeder
{
    public function run()
    {
        factory(App\Document_Person::class, 10)->create()->each(function ($doc) {
        $doc->ispol()->save(factory(App\Ispol::class)->make());
    });
    }
} //It makes 10  Document_Person instances and put it into Person table - this relation  seeds good

as wel as Factory and Seeder for Card and dDcument_Card models

<?php
//Factory for Card Model
use Faker\Generator as Faker;

$factory->define(App\Card::class, function (Faker $faker) {
    return [
        'Number' => $faker->numberBetween($min = 7133, $max = 100000),
        'Note'=>$faker->text($maxNbChars = 60) , 
         ...................................................................
    ];
});

and Factory for Document_Card model

<?php

use Faker\Generator as Faker;

$factory->define(App\Document_Card::class, function (Faker $faker) {
    return [
        'Description'=>$faker->sentence($nbWords = 3, $variableNbWords = true),
        ...................................................................
    ];
});

and Seed at the end)

use Illuminate\Database\Seeder;
class DocumentSeed extends Seeder
{
    public function run()
     {
     factory(App\Document_Card::class, 10)->create()->each(function ($doc) {
        $doc->ispol()->save(factory(App\Card::class)->make());
     });
   }
}//It makes 10 Document_Card records and put id's into Card table - so this relation seeding good as well

But question is: how to seed

  public function Person()
    {
        return $this->hasMany('App\Person','card_id');
    }

relation together with another relations ?? Please help

0 likes
8 replies
Punksolid's avatar

I think I will understand better your question if you put the migrations. The models looks strange to me. However, whats the ultimate model that are you wanting to create?

Does this helps?

$person = factory(App\Person::class)->create([
    'card_id' => factory(Card::class)->create()->id,
    'doc_id' => factory(Document_Person::class)->create()->id
]);
1 like
jenya's avatar
Level 2

@Punksolid, thank you for your answer! Your advice helps me, also I use closure into factory, for exapmle

use Faker\Generator as Faker;

$factory->define(App\Card::class, function (Faker $faker) {
    return [
         'Number' => $faker->unique()->numberBetween($min = 7133, $max = 10000),
          ..........................................
         'Doc_id' => function() {
            return factory(App\Document::class)->create()->id;
           //I have a Document factory described below , so I can use it here
        },  
    ];
});
 

I have factories for Document and Document_Person models as well and I use it inside another factories

use Faker\Generator as Faker;
$factory->define(App\Person::class, function (Faker $faker) {

    return [
        'card_id' =>  function() {
            return factory(App\Card::class)->create()->id;
        },
        'doc_id' => function() {
            return factory(App\Document_Person::class)->create()->id;
        }, //I have a Document_Person factory described below , so I can use it here as well
        .............................................................
        ];
});
 

and seeding PersonSeed

<?php

use Illuminate\Database\Seeder;

class PerosonSeed extends Seeder
{
    public function run()
    {
     
        factory(App\Person::class, 300)->create();
    }
}

But problem is: seeding with factories work good and makes related records in my four tables in correct way! But it all makes 300 records in Person table, 300 records in Card table , but because Card hasMany Person, I want to create 300 Card records and 3 Person records for each Card records, but I still don't understand how to do it

Punksolid's avatar

If I understood the your problem right, I think this is the solution

I want to create 300 Card records and 3 Person records for each Card records, but I still don't understand how to do it

So 300 cards and each card should have 3 persons related


factory(Card::class,300)->create()->each(function($card) {
            factory(Person::class,3)->create([
                "card_id" => $card->id
            ]);
        });

let me know if it works

1 like
jenya's avatar
Level 2

@PUNKSOLID - Your solution works great! Thank you. But when i seed my tables I have another problem: Takes a lot of time to display large amount of related records in view (but if I display the same dataset with dd() function or return it into json - it takes a few seconds..) And I can't use pagination because it can't display eager loaded records correctly..

Punksolid's avatar

@JENYA - First of all, how are you calling the info that you are displaying? Shouldn't take too much nor should show the info wrong with pagination. I recommend you to use pagination. So explain what is it wrong to help you.

However you could do this things to optimize the response:

1.- But to optimize your query you should eager load the info. 2.- Set foreign keys in database. 3.- Specify in the get() the fields you want to retrieve.

jenya's avatar
Level 2

@PUNKSOLID, thank you for a answer! I call data like this:

 $records_all = Card::with(['Document',
                            'Person' => function ($q) {        
                                         $q->where('o_id','=',auth()->user()->o_id)
                                           ->with(['department', 'state']);
                                         }
                         ])->get();
   
  return view('cards.index ',['records_all' => $records_all]);

In this example table Documents has a 765 rows, Person 3500 rows, departments and states has 5 or 8 rows. And main table Cards has a 500 rows, and it has only fileds I need to work with, there are no unused fileds in all tables.

So, following to your advise 1,2 and 3 to optimize the response :

  1. I use eager loading
  2. I also use foreign keys in my DB (I use MS SQL Server v 2012 ent.)
  3. I optimize DB, so there are no unused fileds in all tables.

applying it , my app main page loaded in 1.62 s, of which 93.6 ms takes 20 queries (according to Debugbar info panel) (7-9 of them is light queries to another tables such as user table or user avatar loading). I use Firefox web browser and loading view with data takes 450-500 mb of RAM (I can see it in task manager, client OS in windows 7 Pro). Also I use datatables.net js lib on client side to manipulate data on client side, but I don't think it increase page loading a lot.

Is it normal for Laravel app to display such amount of data for in 1.62 s, of which 93.6 ms takes 20 queries and use 500 mb of RAM ?

BUT if I dump( $records_all) and load this collection, application boot for 514.8 ms (three times faster - according to Debugbar info panel - working with my app in debug mode, so it can increase page loading a little bit) and 11 queries takes 93.6 ms loading as well.

If I run db seed once or twice all data from db will be loaded for 109.2ms (according to Debugbar).

So, you thinkis is it normally to load view with data in 1.62 s and all DB data in 109.ms (near 1200 rows in main table and 5000-6000 rows in related) and Firefox takes 500 mb of RAM?

Is my problem on serverside or on the clientside?? (Sqlserver process in serverside takes only 100-200 mb of RAM) and sorry for my english))

Punksolid's avatar
Level 25

Honestly you caught me unarmed, I haven't taken care so much about the time responses on my projects, I only try to implement good practices.

It looks that that time isn't bad. 'Average Page Loads 2018'

Also you are sending the complete view in your response, so it looks fair. It looks that you are doing the query right. So maybe you could do some steps further to get lower response times.

1 Cache the eloquent call and flush it in the process of storing. 2 Cache the entire HTTP response with (https://github.com/spatie/laravel-responsecache)

I only tried the first one with good results.

Maybe you could also translate the eloquent query to a DB query.

Let me know if you could lower the response time.

Note: I almost forget, are you using PHP 7.3?

1 like
jenya's avatar
Level 2

@PUNKSOLID - I use php 7.3.5. Due to this wonderfull package I increase responce time in many times (I use Redis as default cache). 5000 records loading per 5 ms! Thank you for a great idea!

Please or to participate in this conversation.