Contain Laravel 8 rightJoin data in its own property - laravel

I'm successfully doing a rightJoin in Laravel 8 Eloquent. I'd like to isolate data from the table I am joining as its own property on the object.
My Eloquent code:
return Book
::rightJoin('tableB', 'tableB.book_id', '=', 'book.id')
->where('tableA.user_id', $uid)
->get();
Right now the data I am getting is formatted as e.g.:
[
tableA.col1,
tableA.col2,
tableB.col1,
tableB.col2
]
I'd like to have the returned objected structured like this instead:
[
tableA.col1,
tableA.col2,
tableB [
col1,
col2
]
]
I'm doing this to get a list of books that the user has started, and want the progress info as its own property to satisfy the structure expected on the front end.
I could of course process and restructure the data using PHP on the back end, but this feels rather ugly. Is there a Laravel 8 Eloquent feature that can help me structure the returned data in the way the front end right now expects?

What you are looking for is a belongsTo (or hasOne depending on the case) relationship.
Models/Book.php
public function tableB() {
return $this->belongsTo(TableB::class);
}
Then when on the eloquent builder you can use with to retrieve the books and the TableB records associated with each book
$books = Book::whereHas('tableB', function($query) {
$query->where('id', ...); // this is a query that will put a constraint of which results are returned. Adjust according to your necessity
})->get();

Related

Get data on relation 'B' where relation 'A' does not exits in same id in laravel eloquent

I have two tables named "Student" and "Subscription". If there is at least one active plan in the subscription database, I check by date. I need to get the data if no plan is active. Below is the code I used to get the data. But this is wrong because in this query I am getting all the expired data and I should get the data only if there is not even one plan active in the student id.
$expired_student = Student::wherehas('getSubscription', function ($query) use ($current_date){
$query->where('expired_at','<',$current_date);
})->where('status',1)->count();
anyone can please help me to solve this problem
In you Student model, you can define a relationship method called activeSubscriptions, like this:
public function activeSubscriptions() {
return $this->hasMany(Subscription::class)->where(function($query) {
$query->whereNull('expired_at')->orWhere('expired_at', '>', now());
});
}
And you can use this function like this:
$expiredStudents = Student::doesntHave('activeSubscriptions')->get();

Wrong ID returned from local Eloquent scope

In my Laravel project I've got a model (and an underlying table) for lessons. Now I'm trying to write a local scope for returning all lessons that have been finished by a particular user. The definition of "finished" is that there exists a row in a table named "lesson_results" with this lesson's ID and the users ID.
My scope currently looks like this:
public function scopeFinished($query, User $user)
{
return $query->join('lesson_results', function($join) use($user)
{
$join->on('lesson_results.lesson_id', '=', 'lessons.id')
->where("user_id", $user->id);
});
}
This kinda works. When I do a Lesson::finished($user)->get() I get out the correct lessons for that user. However the lessons in the collection returned all have the wrong ID's! The ID's I see are the ID's from the lesson_results table. So when I check $lesson->id from one of the returned items I don't get the ID from that lesson, but the ID from the corresponding row in the lesson_results table.
I've checked in mysql and the full query sent from Laravel is the following
select * from `lessons` inner join `lesson_results` on `lesson_results`.`lesson_id` = `lessons`.`id` and `user_id` = 53
This query DO return two columns named id (the one from the lessons table and the one from the lesson_results table) and it seems Laravel is using the wrong one for the result returned.
I don't know if I'm going about this the wrong way or if it's a bug somewhere?
This is on Laravel 7.6.1.
edit: Ok, I think I actually solved it now. Not really sure though if it's a real solution or just a workaround. I added a select() call so the return row now is
return $query->select('lessons.*')->join('lesson_results', function($join) use($user)
...which makes it only return the stuff from the lessons table. But should that really be needed?
One of the same column names will be covered by the other.
Solution 1:
Specify the table with the column, and alias the other table's column if it has same column name.
Lesson::finished($user)->select('lessons.*', 'lesson_results.id AS lesson_result_id', 'lesson_results.column1', 'lesson_results.column2',...)->get();
Solution 2:
Or you can use Eloquent-Builder eager-loading whereHas,(Assuming you have build the relationship between model Lesson and model LessonResult)
public function scopeFinished($query, User $user)
{
return $query->whereHas('lessonResults', function($query) use($user)
{
$query->where("user_id", $user->id);
});
}
So you can get lesson like this:
Lesson::finished($user)->get();

Laravel: How to retrieve this nested model

In Laravel I have ModelA, ModelB and ModelC. ModelA has many ModelB. ModelB has many ModelC. I want to retrieve all ModelC for a selection of ModelA. How do I do this?
I tried the following:
$models = ModelC::with(['modelB','modelB.modelA' => function ($query) {
$query->where('owner', 123);
}])->get();
But the first query in that case is select * from model_c. Obviously not the result I am looking for.
Imagine that you were received 100 objects from the database, and each record had 1 associated model (i.e. belongsTo). Using an ORM would produce 101 queries by default; one query for the original 100 records, and additional query for each record if you accessed the related data on the model object. In pseudo code, let’s say you wanted to list all published authors that have contributed a post. From a collection of posts (each post having one author) you could get a list of author names like so:
$posts = Post::published()->get(); // one query
$authors = array_map(function($post) {
// Produces a query on the author model
return $post->author->name;
}, $posts);
We are not telling the model that we need all the authors, so an individual query happens each time we get the author’s name from the individual Post model instances.
Eager Loading
As I mentioned, ORMs “lazy” load associations. If you intend to use the associated model data you can trim that 101 query total to 2 queries using eager loading. You just need to tell the model what you need it to load eagerly.
Here’s an example from the Rails Active Record guide on using eager loading. As you can see, the concept is quite similar to Laravel’s eager loading concept.
$posts = Post::with('author')->limit(100)->get();
I find that I receive better understanding by exploring ideas from a wider perspective. The Active Record documentation covers some examples that can further help the idea resonate.
I managed to solve this with nested whereHas calls as follows:
$models = modelC::whereHas('modelB', function ($query) {
$query->whereHas('modelA', function ($query) {
$query->where('owner', 123);
});
})->get();
Laravel to the rescue, yet again!

Laravel complicated relationship

My laravel app structed like this:
People: Model & Controller
Pets: Model & Controller. Has many to many relationship with People.
Abiilities: Model & Controller.
People_pets: People pets. (pivot table with people_id and pet_id). Also has 4 columns of abbility1_id, abbility2_id, abbility3_id and abbility4_id.
Now.. I built an API method to return the user pets, and it looks like this:
public function pets()
{
return $this->belongsToMany(Pet::Class, 'user_pets')->withPivot(
'abbility1_id',
'abbility2_id',
'abbility3_id',
'abbility4_id'
)->orderBy('position','asc');
}
which makes $user->pets returns the user list of pets, with the pivot information of user_pets and the abbility ids.
The question
What I want to do is add a json object to this method result named "abbilities" and get the data of the abbilities there such as name and description from the Abbilities model/controller.
How I can add the abbilities information to my query out of just the ID's?
The output of the current API CALL:
Desired output: array of Abbilities inside every pet object with the detail of the attack_id's inside $user->pets->pivot->attack1_id
You could just use the query builder and orWhere to grab the data. I don't think you can access it through relationships with the way you have it set up.
I don't know how you access your pet ability ids, but I'm guessing it's like $pet->abbility1_id.
$abbilities = Abbilities::where('id', '=', $pet->abbility1_id)
->orWhere('id', '=', $pet->abbility2_id)
->orWhere('id', '=', $pet->abbility3_id)
->orWhere('id', '=', $pet->abbility4_id)
->get();

Laravel 5 eager loading, select column from eloquent relationship

I am trying to
1. select `DOS`,`name`,`fin`,`ins_type` from events table.
2. site_name from sites table
3. client_name from clients table
but unable to access site_name, client_name column in select statement
->select('DOS','name','fin','ins_type')
How to add these columns site.site_name, client.client_name in above select statement.
Eloquent query is
$events = Event::with([
'site'=>function($q){
$q->select('site_id','site_name','client_id');
},
'site.client'=>function($q2){
$q2->select('client_id','client_name');
}])
->select('DOS','name','fin','ins_type')
->get();
How about this?
$events = Event::with([
'site'=>function($q){
$q->with(['client'=>function($qq){
$qq->select('client_id','client_name');
}])->select('site_id','site_name','client_id');
}])
->select('DOS','name','fin','ins_type')
->get();
I found a simple solution from the comment section in here. It is possible to put the column selection in the model. So in your case, your Event model could be something like this.
public function site()
{
return $this->belongsTo('App\Site')->select('id', 'name', 'client_id');
}
Then, in your controller you simply call the function.
$events = Event::with('site')->get();
I haven't tried how this goes with the nested eager loading though.
I myself got a little problem with these also until i discover that you need to add the foreign key to the select statement inside the "with" closure.
the column that relates "client" to "site" and "site" to "Event"

Resources