I'm trying to order the tests for each user in descending order of created_at. I tried it in the template but I didn't succeed. These are my tables:
| users | | courses | | tests |
| ---------- | |------------| |------------|
| id | | id | | id |
| name | | name | | name |
| created_at | | created_at | | created_at |
| user_id | | course_id |
A user has many courses and a course has many tests. I'll like to order all the tests in descending order of created_at.
I tried this in my template:
#foreach(User::find($user->id)->courses as $course)
#foreach(Course::find($course->id)->tests as $test)
<p>Name: {{$test->name}}</p>
<p>Date: {{$test->created_at}}</p>
#endforeach
#endforeach
Edit: There are my models
User.php
public function courses()
{
return $this->hasMany('Course');
}
Course.php
public function user()
{
return $this->belongsTo('User');
}
public function test()
{
return $this->hasMany('Test');
}
Test.php
public function courses()
{
return $this->belongsTo('Course');
}
You may try this, do it in your Controller:
$users = User::with(array('courses.tests' => function($query) {
$query->orderBy('tests.created_at', 'desc');
}))->find($user->id);
Then load the view and pass the $user object like this:
return View::make('your_view_name')->withuser($user);
Then in your view try something like this:
#foreach($user->courses->tests as $test)
<p>Name: {{ $test->name }}</p>
<p>Date: {{ $test->created_at }}</p>
#endforeach
You could simply add the orderBy command to your query, like:
#foreach($user->courses as $course)
#foreach($course->tests()->orderBy('created_at', DESC)->get() as $test)
<p>Name: {{$test->name}}</p>
<p>Date: {{$test->created_at}}</p>
#endforeach
#endforeach
Look that it's not a good practice to make queries in the views, so I changed your query in some variables you should be setting in your controller.
Also change test() method to tests(), because it's a one-to-many relation, and it's clearer to have plurals as method name.
Related
I have a small Laravel app that is basically a few tables as follows and I'm having a problem with Laravel Eloquent generating more SQL queries than it needs. I am basically trying to display all 'grades' for a student that is logged in but to keep the queries to a minimum.
Here is my controller
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->grades->paginate(5)
]);
}
Here are my table structures
courses
select id, name from courses;
+----+--------------------+
| id | name |
+----+--------------------+
| 1 | English Literature |
| 2 | Stats |
| 3 | Biology |
+----+--------------------+
grades
select id, student_id, course_id, score, letter_grade from grades where student_id = 1;
+----+------------+-----------+--------+--------------+
| id | student_id | course_id | score | letter_grade |
+----+------------+-----------+--------+--------------+
| 1 | 1 | 1 | 90.00 | A |
| 2 | 1 | 2 | 50.00 | D |
| 3 | 1 | 3 | 100.00 | A |
+----+------------+-----------+--------+--------------+
course_student
select * from course_student;
+-----------+------------+
| course_id | student_id |
+-----------+------------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
+-----------+------------+
students
select id, email from students limit 1;
+----+---------------------------+
| id | email |
+----+---------------------------+
| 1 | bob#myapp.com |
+----+---------------------------+
My Laravel relations in the models look like the following:
Student.php
public function grades(): \Illuminate\Database\Eloquent\Relations\HasMany
{
return $this->hasMany(Grade::class);
}
public function courses(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Course::class);
}
Grade.php
class Grade extends Model
{
use HasFactory;
public function student(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Student::class);
}
public function course(): \Illuminate\Database\Eloquent\Relations\BelongsTo
{
return $this->belongsTo(Course::class);
}
}
Course.php
class Course extends Model
{
use HasFactory;
public function department(): \Illuminate\Database\Eloquent\Relations\HasOne
{
return $this->hasOne(Department::class, 'id', 'department_id');
}
public function students(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Student::class);
}
public function teachers(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
{
return $this->belongsToMany(Teacher::class);
}
}
DashboardController.php
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->load('grades', 'courses')->grades->paginate(10)
]);
}
dashboard/index.blade.php view
<div class="py-2">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
<div class="bg-white dark:bg-gray-800 sm:rounded-lg">
{{ $grades->links() }}
<table>
<tr>
<td>Course</td>
<td>Grade</td>
<td>Score</td>
<td>Date</td>
</tr>
#foreach ($grades as $key => $grade)
<tr>
<td>{{ $grade->course->name }}</td>
<td>{{ $grade->letter_grade }}</td>
<td>{{ $grade->score }}</td>
<td>{{ $grade->created_at }}</td>
</tr>
#endforeach
</table>
</div>
</div>
</div>
Using Laravel Debugbar I can see all of the queries it has generated - I don't have any errors but the SELECT * from courses could be optimised to use an IN() rather than an entire SELECT * from courses by primary key each time, how do I optimise my controller/model logic
select * from `students` where `id` = 1 and `students`.`deleted_at` is null limit 1
select * from `grades` where `grades`.`student_id` = 1 and `grades`.`student_id` is not null
select * from `courses` where `courses`.`id` = 1 limit 1
select * from `courses` where `courses`.`id` = 2 limit 1
select * from `courses` where `courses`.`id` = 3 limit 1
select * from `courses` where `courses`.`id` = 4 limit 1
select * from `courses` where `courses`.`id` = 5 limit 1
Expected output to use fewer queries to the courses table & to NOT lazy load queries
Your pagination is not hitting a queryBuilder but a Collection instead: Doing $student->grades already retrieves all the grades of the student, that's why the query has no limit in it.
You are using the courses relation of Grade::class in your blade yet you loaded the courses relation of Student::class in your controller.
To improve everything, you can do it like this
public function index(Request $request)
{
$student = $request->user();
return view('dashboard.index', [
'user' => $student,
'grades' => $student->grades()->with('courses')->paginate(10),
]);
}
Calling $student->grades() returns a QueryBuilder instead of calling $student->grades which returns a Collection
You have done excellent job setting up relationships in each Models.
For your question , you can achieve this using nested eager loading.
$student = Student::query()
->where('id', auth()->id())
->with('grades.courses')
->first();
$grades = $student->grades->paginate(5);
return view('dashboard.index', compact('student', 'grades'));
As documented here : https://laravel.com/docs/9.x/eloquent-relationships#nested-eager-loading
Now your laravel should run only 3 queries on table students, grades and courses.
And if you want only to get grades with pagination, try run this query.
$user = $request->user(); // or auth()->user()
$grades = Grade::query()
->where('student_id', $user->id)
->with('courses')
->paginate(5);
return view('dashboard.index', compact('user', 'grades'));
This still will run only 3 queries.
note: $request->user() is the same as auth()->user()
and in the query i use auth()->id() to get the logged in user Id
I'm new to Laravel, and I got stuck with the following issue:
I have a table for the users, and groups, and a table for connecting them. The general task any user can join any group.
----------------------------------------------
| users | groups | user_groups |
|--------------------------------------------|
| id - int pk | id - pk | id - pk |
| name text | name | user_id - fk |
| email | | group_id - fk |
| phone | | any_attr |
----------------------------------------------
I have the following models:
class User
{
...
public function groups()
{
return $this->belongsToMany(Group::class, 'user_groups')->withPivot(['is_notification_requested']);
}
...
}
class Group
{
...
public function users()
{
return $this->belongsToMany(User::class, 'user_groups');
}
...
}
How do I get all of the groups, with a count of the members? I need the Group model, and a count of the users in the group.
If you're using Laravel 5.3, you can simply add withCount('relationship') as documented here: https://laravel.com/docs/5.3/eloquent-relationships#counting-related-models
Here's an example following your code:
$groups = Group::withCount('users')->get();
Now you can do this:
foreach($groups as $group) {
echo $group->user_count
}
Imagine following DB tables:
Clients Users
| Id | name | user_id | | ID | name |
| -- | ----- | ------ | | -- | ------ |
| 1 | NULL | 1 | | 1 | David |
| 2 | Peter | NULL |
Not each client is automatically user, but user may have client account. Users table contains only registered users, if user is client (dont have to be) the client account is created.
I have succcessfully made relations between these table is Laravel. Eloquent querying woks fine, eg.:
clients.php
public function userAcc() {
return $this->hasOne('User', 'id', 'user_id');
}
user.php
public function clientAcc() {
return $this->hasOne('Clients', 'user_id', 'id');
}
The problem is that when i query these tables:
Clients::with('userAcc')->get()
In view i have to make lots of ifs, like:
#if($client->userAcc)
{!! $client->userAcc->name !!}
#else
{!! $client->name !!}
#endif
Is there any workaround in laravel that allows me to format collections on each select query? Something like Accessor but on whole collection, so i will be able to do something like this in view:
{!! $client->realname !!}
And it writes client name from particular table.
Many thanks for any suggestions
This is the Laravel way to accomplish this:
class Client extends Model {
public $appends = ['realname'];
public function userAcc() {
return $this->hasOne('User', 'id', 'user_id');
}
public function getRealnameAttribute(){
return $this->userAcc? $this->userAcc->name : $this->name;
}
Now it will be accessible through $client->realname and it will be included in $client->toJson() and $client->toArray()
You can make a method for that:
public function getName()
{
return $this->userAcc?$this->userAcc->name:$this->name;
}
In your blade just call the function:
{{$client->getName()}}
Is this classed as a belongs to / belongstomany / hasmany etc?
I have a table in my DB which contains all car makes:
ID | name | display_name
1 | audi | Audi
2 | bmw | BMW
I also have a vehicles table:
ID | VRM | make
1 | HW55VRM | 2
2 | VN62HHS | 1
What I wish to do is when returning all of the vehicles in my repository like so:
public function getAll()
{
return Vehicle::all();
}
What I wish to do is convert the make value to match that in the makes table and return the display name for it. So in my view I can simply call the display name as the text value.
You didn`t show relations between vehicles and makers, so for example it is like
class Vehicle
{
public function maker()
{
return $this->hasOne('Makers');
}
}
Then in blade
#foreach(Vehicle::with('maker')->get() as $vehicle)
{{ $vehicle->maker->display_name }} {{ $vehicle->VRM }}
#endforeach
I try create simple model with Laravel and Eloquent.
Here my code example:
composer.json
"laravel/framework": "4.1.*"
routes.php
Route::controller('items', 'ItemsController');
Route::get('item/{item}', array('as'=>'item', 'uses'=> 'ItemsController#getView'));
php artisan route:
| | GET item/{item} | item | ItemsController#getView | | |
| | GET items | | ItemsController#getIndex | | |
controllers/ItemsController.php
<?php
class ItemsController extends BaseController {
public function getIndex()
{
$items = Item::all();
return View::make('items.index')
->with('title', 'Index show')
->with('items', $items);
}
public function getView($id)
{
return View::make('items.view')
->with('title', 'View show')
->with('items', Item::find($id));
}
}
models/Item.php
<?php
class Item extends Eloquent {
protected $table = 'items';
}
DB: I have MySQL table "items" with 6 rows, for example:
+----+-----------+---------------------+
| id | name | created_at |
+----+-----------+---------------------+
| 4 | ironman | 2012-04-03 10:02:44 |
| 5 | robot | 2012-04-13 10:02:44 |
+----+-----------+---------------------+
When I try to GET mydomain/item/2
Laravel say:
Call to undefined method Item::find()
And GET mydomain/items/
Call to undefined method Item::all()
What I'm missed?
It sometimes happens to me when I am changing model and table names "on the fly", and when I do a lot of migration rollback. Try to rename you model name, it works for me in most cases.