One To Many associate - laravel

I have a model Shop and a model Comment.
on the Comment model:
public function shop()
{
return $this->belongsTo('App\Shop', 'commentable_id');
}
The Shop model is:
public function comments() {
return $this->hasMany('App\Comment', 'commentable_id');
}
So the relationship is One To Many
I want to copy all the comments of a Shop and attach them to another Shop:
$shop = Shop::find(1);
$comments = $shop->comments()->pluck('id')->toArray(); // it works, i have all the ids of the comments
$othershop = Shop::find(2);
$othershop->comments()->associate($comments); // doesn't work
$othershop->comments()->attach($comments); // doesn't work
Anyone knows how to proceed with a One to Many situation?

You can utilize createMany method of relationship:
$othershop->comments()->createMany($shop->comments->toArray());
You can also utilize chunking if one shop can have many comments (split it into more queries to make it more memory efficient):
$shop->comments()->chunk(500, function ($comments) use ($othershop) {
$othershop->comments()->createMany($comments->toArray());
});
edit: changed my answer based on comments.

To copy comments, you first need to replicate them. Then you can associate them with their owning model:
$shop = Shop::find(1);
$otherShop = Shop::find(2);
$comments = $shop->comments()->get();
foreach ($comments as $comment) {
$tmp = $comment->replicate();
$tmp->shop()->associate($otherShop);
$tmp->save();
}

Related

How to retrieve multiple relations with multiple tables in laravel eloquent

I'm using Laravel 5.8 to build a babysitting site. I have 4 tables with different relationships as below:
please see this image
The relationships are:
Babysitter->hasMany(session)
Sessions->hasOne(Review)
Sessions->hasOne(Kids)
Sessions->hasOne(Babysitter)
Sessions->hasOne(Parent)
I want to achieve 2 things:
First one
I want to show this result when listing all babysitters. I'm showing this information for each babysitter:
plsease see this image
See here what I couldn't achieve
plsease see this image
This is my code
Sitters::where('Status', 'active')->where('Verified', 1)->get();
Second one
Also, I've tried to show kids name with parent review as shown here:
plsease see this image
This is what i'm using
Sessions::select('Reviews.*', 'Sessions.Parent_id')->join('Reviews', 'Reviews.Session_id', '=', 'Sessions.id')->with('owner')->where('Trainer_id', session('user')->Id)->where('Status', '=', 'complete')->with('owner')->orderBy('Sessions.id', 'DESC')->get();
Here is Session.php Model
public function owner(){
return $this->belongsTo('App\Models\Parents', 'Parent_id');
}
As discussed change the relations:
Babysitter->hasMany(sesstion)
Sessions->hasOne(Review)
Sessions->belongsTo(Kids)
Sessions->belongsTo(Babysitter)
Sessions->belongsTo(Parent)
First one
in Babysitter.php declare the following attributes
class Babysitter extends Model
{
public function reviews()
{
$this->hasManyThrough(Review::class, Session::class);
}
public function getAverageReviewAttribute()
{
return $this->reviews()->avg('Rating');
}
}
Then you just need to call it on the model instance.
$babysitter = Babysitter::first();
return $babysitter->average_review;
Second one
Just use the relation
$babysitter = BabySitter::with(['sessions' => public function ($session) {
$session->with(['review','parent','kids']);
})->where('trainer_id', '=', session('user')->Id) //did not understand this condition
->first();
This assumes you have parent, kids and review relation declared on Session::class. (change the names if needed)
After a few days of searching & testing, this is what worked for me:
Inside (Sitters) Model, put this relation
public function sessions()
{
return $this->hasMany(Sessions::class, 'sitter_id')
->withCount('reviews')
->withCount(['reviews as review_avg' => function($query){
$query->select(DB::raw('AVG(Rating)'));
}]);
}
Also, inside (Sessions) Model, put this relation
public function reviews()
{
return $this->hasOne(Reviews::class, 'Session_id');
}
Now you query like this
return $sitters = Sitters::with('sessions')->get();
I hope this can help someone :)

Laravel Relationships - Select rows with same column value in blade

Let's say I have a post with many comments and I properly defined $post->comments relation in my post model. Comments have a column named confirmed with the value of 0 or 1. How can I select confirmed (rows which have confirmed value of 1) rows in my blade template?
This can help you
In your Post model
public function comments()
{
return $this->hasMany(Comment:class);
}
public function confirmedComments()
{
return $this->comments()->whereConfirmed(1);
}
And from your controller
$comments = $post->confirmedComments;
If in blade, you want select confirmed comments, it's so easy to do it with
#if($comment->confirmed)
//confirmed comment
#else
//
#endif
Hope it'll be helpful !
There are many ways to do this.
If you already have the comments eager loaded, then you can use the where() method on the comments collection:
$confirmedComments = $post->comments->where('confirmed', 1);
This will go through all the comments in the collection and only return those that are confirmed.
If you don't have the existing collection, you can add the where clause to the relationship query. This way, you're only getting confirmed comments from the database, and you're not wasting resources retrieving and building models for comments you're not using
$confirmedComments = $post->comments()->where('confirmed', 1)->get();
And, another option, would be to create a new relationship for just confirmed comments, and that way you can eager load the new relationship:
public function comments()
{
return $this->hasMany(Comments::class);
}
public function confirmedComments()
{
return $this->comments()->where('confirmed', 1);
}
$confirmedComments = $post->confirmedComments;

Doing ->save to multiple relations

I am using Laravel 5.5.13.
I have a model called Thumb. This thumb is related to two things in a many-to-one relationship: Player and Comment.
I currently do things like this:
public function store(Request $request, Entity $entity, Player $player)
{
$thumb = new Thumb($request->all());
$thumb->player_id = $player->id;
$entity->thumbs()->save($thumb);
return response()->json($thumb, 201);
}
We see how I have to set $thumb->player_id AND I don't have to set the entity_id because I am doing $entity->thumbs()->save
Is there a way to do $entityAndPlayer->thumbs()->save? Or is the way I did it above the recommend way?
You cannot use relationships to set 2 foreign columns so they way you showed is the correct one. However you can make it a bit cleaner in my opinion.
Into Thumb model you could add:
public function setPlayer(Player $player)
{
$this->player_id = $player->id;
}
and then instead of:
$thumb->player_id = $player->id;
you could use:
$thumb->setPlayer($player);
Or you could add create setPlayerAttribute method and finally instead of:
$thumb = new Thumb($request->all());
$thumb->player_id = $player->id;
use just:
$thumb = new Thumb($request->all() + ['player' => $player]);
You can't save multiple relationships at once but, for many to one associations you can use the method associate() (Laravel docs) to save using the belongs to part of the relationship, for example:
public function store(Request $request, Entity $entity, Player $player)
{
$thumb = new Thumb($request->all());
$thumb = $thumb->save();
$thumb->player()->associate($player);
$thumb->entity()->associate($entity);
return response()->json($thumb, 201);
}

Paginate with Eloquent but without instantiation Models

We have two Models:
SimpleModel (id, country, code)
ComplexRelatedModel (id, name, address)
SimpleModel has many ComplexRelatedModel, then
class Product extends Model
{
protected $fillable = [
'name'
];
/* hasOne */
public function complexRelatedChild()
{
return $this->hasOne(self::class, 'parent_id', 'id');
}
}
If we do
$simples = SimpleModel
->with('complexRelatedChild')
->simplePaginate(100000 /* a lot! */);
And we need only do
foreach ($simples as $simple) {
echo $simple->complexRelatedChild->name;
}
Any ComplexChild has hydratated and ready. This takes a lot of memory in my case. And we need just one field without any funciton or feature of Model.
It's possible use some data field from related object or with eloquent this isn't possible?
Not sure I completely understand your question. You want to only load one field from the complexRelatedChild relation to keep memory limit down?
You could do:
$simples = SimpleModel::with(['complexRelatedChild' => function($query){
return $query->select(['id', 'name']);
})
->simplePaginate(100000);
Which can be simplified to:
$simples = SimpleModel::with('complexRelatedChild:id,name')
->simplePaginate(100000);
However if I were you, I would try to paginate less items than 100000.
Update:
You could use chunk or cursor functions to process small batches of SimpleModel and keep memory limit down.
SimpleModel::chunk(200, function ($simples) {
foreach ($simples as $simple) {
}
});
or
foreach (SimpleModel::cursor() as $simple) {
}
See the documentation for more information

How to sort a query by the results of a model's method in Laravel

I have a model that has a "hasMany" relationship with a second model which records "votes" for the former. I then have a method which can be called upon to simply count the number of votes, as follows:
class Car extends Model
{
public function votes()
{
return $this->hasMany(Vote::class);
}
public function score()
{
return $this->votes->count();
}
}
In my controller, I want to run a query that returns all() but sorts by score()... something along the lines of:
$cars = Car::all()->sortBy('score');
But that doesn't work obviously... I feel like I'm on the right track, but don't quite know how to make this work. Would appreciate some help. Thanks.
Try this solution:
$cars = Car::with('votes')->get()->sortBy(function($car)
{
return $car->votes->count();
});
Hope it help you :)
the code should be like this :
$products = Shop\Product::join('shop_products_options as po', 'po.product_id', '=', 'products.id')
->orderBy('po.pinned', 'desc')
->select('products.*') // just to avoid fetching anything from joined table
->with('options') // if you need options data anyway
->paginate(5);

Resources