Building complex Eloquent query using belongsToMany relationship, foreign table and whereIn() - laravel-5

In a Laravel 5 app, I have 5 tables - users, books, authors, followers and activity_feeds.
Users can follow authors and a book can have several authors.
When a book is made, an activity_feeds entry is made that references the book_id.
I need to build an eloquent query to get a collection of activity_feeds for each users, to iterate over in their home page activity feed.
My Book model includes
public function authors()
{
return $this->belongsToMany('App\Author')->withTimestamps();
}
The activity_stream table looks like this (with example data)
id (1)
user_id (3)
type (New Book)
book_id (18)
created_at etc
and my User controller includes
public function feedItems()
{
return $this->hasMany('App\ActivityFeed');
}
public function userFollowings()
{
return $this->belongsToMany('App\User', 'followers', 'follower_id', 'subject_id')->withTimestamps();
}
public function authorFollowings()
{
return $this->belongsToMany('App\Author', 'followers', 'follower_id', 'author_id')->withTimestamps();
}
My current query (which isn't working), contained in the User model is
public function getactivityFeedsAttribute()
{
$userFollowings = $this->userFollowings()->pluck('subject_id')->toArray();
$authorFollowings = $this->authorFollowings()->pluck('author_id')->toArray();
$userFeeds = ActivityFeed::whereIn('user_id', $userFollowings)
->orwhereIn('book_id', function($query){
$query->select('id')
->from(with(new Book)->getTable())
->whereHas->authors()
->whereIn('id', $authorFollowings);
})
->get();
return $userFeeds;
}
$userFollowings and $authorFollowings are working fine.
I'm not sure I'm using the correct syntax for data[book_id] to pluck the book id from the activity_feeds row, and I'm really not sure if I can nest a table look up or use $query like this.
It also seems VERY complicated. Am I might be missing something much more straight forward?
In the blade I am calling like this
#forelse ($user->activityFeeds as $activityFeed)
<div class="row">
<div class="col-2">
{{ $activityFeed->user->firstname }}
</div>
<div class="col-2">
{{ $activityFeed->type }}
</div>
</div>
<hr>
#empty
No activity yet
#endforelse
Which works if I just query 'ActivityFeed::whereIn('user_id', $userFollowings)'

I'll rewrite the query in an answer because comments aren't very legible.
public function getactivityFeedsAttribute()
{
$userFollowings = $this->userFollowings()->pluck('subject_id')->toArray();
$authorFollowings = $this->authorFollowings()->pluck('author_id')->toArray();
$books = Book::whereHas('authors', function ($query) use ($authorFollowings) {
// Have to be explicit about which id we're talking about
// or else it's gonna throw an error because it's ambiguous
$query->whereIn('authors.id', $authorFollowings);
})->pluck('id')->toArray();
$userFeeds = ActivityFeed::whereIn('user_id', $userFollowings)
->orwhereIn('book_id', $books)
->get();
return $userFeeds;
}

Related

Laravel: Passing data to view through route after querying

I recently asked a question about defining many to many relationships (using belongsToMany) and it was a huge help. So now in my models I have:
Users model
public function subjects()
{
return $this->belongsToMany('App\Subject', 'users_subjects', 'user_id', 'subjects_id');
}
Subjects model
public function users()
{
return $this->belongsToMany('App\User', 'users_subjects', 'subject_id', 'user_id');
}
This way I establish a relationship between users and subjects via the users_subjects table. My next step was to create a controller, SubjectsController, which ended up looking like this:
class SubjectsController extends Controller
{
// returns the view where subjects will be displayed
public function index()
{
return view('profiles.professor.prof_didactic_subjects');
}
// get users with subjects
public function getSubjects()
{
$subjects = User::with('subjects')->get();
}
// get a single user with a subject
public function getSubject($id)
{
$materia = User::where('id', '=', $id)->with('subjects')->first();
}
}
I'm not very sure about the code in the controller though.
The final step is where it gets tricky for me, even after reading the docs: I want to pass each result to the view, so I can have multiple tiles, each populated with data from subjects the user is associated with:
#foreach ($subjects as $subject)
<div class="tile is-parent">
<article class="tile is-child box">
<p class="title">{{ $subject['name'] }}</p>
<div class="content">
<p>{{ $subject['description'] }}</p>
</div>
</article>
</div>
#endforeach
I tried many different route configurations, but kept getting either the undefined variable error or trying to access non-object error.
What's the proper course of action here? I feel I'm missing something very basic. Thanks in advance for any help.
The answer
The solution provided below by #Sohel0415 worked perfectly. My index() method on the controller now looks like this:
public function index()
{
// temporary value while I figure out how to get the id of the current user
$user_id = 6;
$subjects = Subject::whereHas('users', function($q) use ($user_id){
$q->where('user_id', $user_id);
})->get();
return view('profiles.professor.prof_didactic_subjects')->with('subjects', $subjects);
}
My route looks like this:
Route::get('/professor', 'SubjectsController#index');
I was pretty lost, so this absolutely saved me, thanks again :)
You need to pass $subjects to your view. You can use compact() method for that like -
public function index()
{
$subjects = Subject::with('users')->get();
return view('profiles.professor.prof_didactic_subjects', compact('subjects'));
}
Or using with() method like -
public function index()
{
$subjects = Subject::with('users')->get();
return view('profiles.professor.prof_didactic_subjects')->with('subjects', $subjects);
}
If you want to get Subject for a particular user_id, use whereHas() -
$subjects = Subject::whereHas('users', function($q) use ($user_id){
$q->where('user_id', $user_id);
})->get();

accessing collections within collections laravel 5

I have a table of "rosters" that's pretty much strictly foreign keys. It acceses the "schools" table, "courses" table, and "students" table. So essentially, a 'student' takes a 'course' at a 'school'. My RostersController has this
public function show($id)
{
$roster = Roster::where('course_id', $id);
return view('roster')->with('roster', $roster);
}
My Roster Model is:
public function students()
{
return $this->hasMany('App\Student');
}
My student Model is:
public function roster()
{
return $this->belongsTo('App\Roster');
}
my view is this:
#foreach ($roster as $child)
<p>{{$child->id}}</p>
<p>{{$child->students->first_name}}</p>
#endforeach
The rosters table just saves the student_id rather than all of the student's data that is already in the 'students' table. So i'm trying to access the students table from this relation but when i run this, it tells me that anything related to the students table is 'not on this collection'. I know that I could do things this way if i was working with a hasOne relationship, but how can i accomplish this with a hasMany to output the students table value in each row?
You should try this
public function show($id)
{
$roster = Roster::with('students')->where('course_id', $id);
return view('roster')->with('roster', $roster);
}
Try this
Roster.php
public function show($id)
{
$roster = Roster::with('students')->where('course_id', $id)->get(); // load the students
return view('roster')->with('roster', $roster);
}
On the view
#foreach ($roster as $child)
<p>{{$child->id}}</p>
<p>
<!-- Loop through the stundents since this returns an collection of students and not just one -->
#foreach($child->students as $student)
{{$student->first_name}} <br>
#endforeach
</p>
#endforeach
Check this for more information on eager loading
The $child->students is a collection. So, you need to use another foreach loop inside the first one.
Your code should look like this:
#foreach ($roster as $child)
<p>{{$child->id}}</p>
#foreach($child->students as $student)
<p>{{$student->first_name}}</p>
<p>{{$student->age}}</p>
<p>{{$sutdent->another_column_in_students_table}}</p>
#endforeach
<p>{{$child->any_other_column_in_roster_table_if_needed}}</p>
#endforeach
So, your first issue is, that
$roster = Roster::where('course_id', $id);
will just return a QueryBuilder instance, you have to use
$roster = Roster::where('course_id', $id)->get();
then for sure, students is a collection, you have to iterate over it like this:
#foreach ($child->students as $student)
{{ $student->yourProperty}} <br>
#endforeach
Update:
I saw you know already about that when to use get() in query laravel 5

How to safely access other users and other model's records inside Blade template

I have 3 models in my app Users, Sales and Plans, when I render sales for each customer (due to storing) I only get id's for other users and models related to that sale (like account manager, owner, plan), now I'm trying to use those ID's inside blade to get names or other rows based on ID and model. Here is the show function:
public function show($id) {
$user = User::find($id);
$sales = Sale::where('customer_id', '=', $id)->get();
return view('profiles.customer', ['user' => $user, 'sales' => $sales]);
}
And in blade I get all those sales like:
#foreach ($sales as $sale)
<li>
<i class="fa fa-home bg-blue"></i>
<div class="timeline-item">
<span class="time"><i class="fa fa-clock-o"></i> {{$sale->created_at->format('g:ia, M jS Y')}}</span>
<h3 class="timeline-header">{{$user->name}} became a customer</h3>
<div class="timeline-body">
<p>Date: {{$sale->sold_date}}</p>
<p>Price: {{$sale->sale_price}}</p>
</div>
</div>
</li>
#endforeach
So inside each record I have like "account_manager_id", "agent_id", "owner_id", "plan_id".
Currently I have this solved by adding public static function (this is for users, have same function for Plan model as well) in Sale model class:
public static function getUser($id) {
return User::where('id', $id)->first();
}
And I'm using it like this in Blade:
Account manager: {{$sale->getUser($sale->account_mgr_id)->name}}
Is this the safest and best way to do it? Or there is something I'm overlooking here?
You need to add relationships in your Sales Model.
class Sales extends Eloquent {
.....
.....
public function accountManager() {
return $this->hasMany('App\User', 'account_manager_id');
}
public function agents() {
return $this->hasMany('App\User', 'agent_id');
}
public function owner() {
return $this->hasOne('App\User', 'owner_id');
}
}
Now $sales->agents will give you a user with agent_id as id in User table.
Update your hasOne, hasMany relationships as your need. Laravel Documentation.
From your blade template, your access your AccountManager as
#foreach($sales->accountManager as $manager)
Name: {{ $manager->name}}
#endforeach
I think you could use Eloquent relationships. Taking your example, you should define relationship in your User model:
<?php
class User extends Eloquent {
public function sales() {
return $this->hasMany(Sale::class, 'customer_id');
}
}
Then, whenever you need to get sales of that user (entries, that relate via customer_id column), just simply do
<?php
$user = User::find($id);
$sales = $user->sales;
This is very fun when when you have to print out list of users that have sales, for example
<?php
public function showUsersWithSales() {
$users = User::with('sales')->get();
return view('users-with-sales', compact('users'));
}
users-with-sales.blade.php example:
#foreach ($users as $user)
User: {{ $user->name }}<br>
Sales:<br>
#foreach ($user->sales as $sale)
{{ $sale->amount }} {{ $sale->currency }} # {{ $sale->created_at }}<br>
#endforeach
<hr>
#endforeach
It would print all users with their sale amount and currency, followed by date when it was created.
This sample takes into account, that your User model has name attribute and your Sale model has amount, currency, created_at and customer_id fields in your database.
To reverse the action, say you have a sale and want to know who made it, just simply define a relationship!
<?php
class Sale extends Eloquent {
public function customer() {
return $this->belongsTo(User::class, 'customer_id');
}
}
Hope that helps!
Eloquent Relationship is your friend, https://laravel.com/docs/5.2/eloquent-relationships and you can solve your problem easily.
Suggestion is to remove all those function access and control from view and put it somewhere else. This will be good habit for you so you can avoid the infamous fat view.

Laravel eloquent grab user with posts ordered by

I am playing around with Laravel and I have this code. It returns all of a users posts, based on the provided username.
Controller
$posts = User::whereUsername($username)->firstOrFail()->posts;
return View::make('users.index')->with($posts);
View
#foreach($posts as $post)
{{ $post->title }}
{{ $post-owner->username }}
// etc etc
#endforeach
How would I go about adding an orderBy() to my query? I would like the posts to be ordered by newest first. This was my attempt :(
User::whereUsername($username)->firstOrFail()->posts->orderBy('created_at', 'DESC');
Or is there a better way to go about doing this?
EDIT
User Model
public function posts()
{
return $this->hasMany('Post');
}
Post Model
public function owner()
{
return $this->belongsTo('User', 'user_id');
}
Your code is slightly missing the () brackets on the relationship!
Try this
User::whereUsername($username)
->firstOrFail()
->posts()
->orderBy('created_at', 'DESC')
->get();
Note the brackets on posts()->

get posts by self and posts by all follows with attached user using with() or joins and order by post created_at

I have a follow system setup from this tutorial.
Creating the Twitter following model in Laravel 4
It works for getting follows and followers and for saving them. But I want to list all of my posts and all posts of the people I follow, along with the related user object for each one and order them all by the posts created_at column.
Rather than try to pick some code to show what I have tried, lets just say I have spent two days trying every combination of join(), leftJoin(), nested joins, where(), orWhere(), nested wheres, with(), joins and wheres nested in with() that I can think of and I just can't figure it out.
For the follows I have a pivot table with user_id and follow_id. Here are the relationships in my User model.
/**
* User following relationship
*/
public function follows()
{
return $this->belongsToMany('User', 'user_follows', 'user_id', 'follow_id')
->withTimestamps();
}
/**
* User followers relationship
*/
public function followers()
{
return $this->belongsToMany('User', 'user_follows', 'follow_id', 'user_id');
}
Twit.php model. (Actually my posts are called twits but same concept)
class Twit extends Eloquent {
protected $fillable = ['twit', 'user_id'];
public function user()
{
return $this->belongsTo('User');
}
}
User.php model
class Twit extends Eloquent {
protected $fillable = ['twit', 'user_id'];
public function user()
{
return $this->belongsTo('User');
}
}
I've tried talking myself through this but none of the eloquent functions seem to do what I think they should do. To be clear, here is verbally what I need to happen.
Get each twit with its user and order by twits.created_at
only where user.id = Auth::user()->id
or where user.id is in Auth::user()->follows
Help writing this out as a raw query would work too.
Thanks for any help.
UPDATE: Deleted my own answer to save others from getting confused by it since it was way off and wasn't working 100%.
The selected answer works perfectly. Here is the selected answer by #philipbrown with added eager loading for the user and ordered by the twit created_at date
$twits = Twit::whereIn('user_id', function($query)
{
$query->select('follow_id')
->from('user_follows')
->where('user_id', Auth::user()->id);
})->orWhere('user_id', Auth::user()->id)
->with('user')
->orderBy('created_at', 'DESC')
->get();
And in the view
#foreach($twits as $twit)
<li>
<div class="twit-gravitar">
<img src="{{ getGravitar($twit->user->gravitar) }}">
</div>
<div class="twit">
<div class="twit-handle">
{{link_to('/twits/'.$twit->user->username, '#'.$twit->user->username) }}
</div>
<div class="twit-text">{{ $twit->twit }}</div>
</div>
</li>
<hr class="twit-separator">
#endforeach
I'll walk through step-by-step how I would solve this problem. I find it easier to get my head around the raw query before I convert that into ORM methods, so I'll write this out as I would work through it, rather than just give you the answer.
Writing the raw query
So first I would simply get all twits (I'm guessing it's twits?):
SELECT * from twits
Next I would refine this by only selecting the from the current user (using user_id 1 as an example):
SELECT * FROM twits WHERE user_id = 1
Next we can use an SQL subselect to find all the users that the current user follows:
SELECT * FROM twits WHERE user_id IN (SELECT follow_id FROM user_follows WHERE user_id = 1) OR user_id = 1
Now if you run that on your database and change the user_id you should get a stream of twits that you were expecting.
Converting to Eloquent
Now that we have the raw query sorted, we can convert it to use Eloquent so you are returned an Eloquent Collection.
Again, first start by simply getting all twits:
$twits = Twit::all();
Next we need to use the whereIn method:
$twits = Twit::whereIn('user_id', array(2, 3, 4))->get();
But instead of passing an array of user ids, we need to pass a Closure so we can do the subselect:
$twitss = Twit::whereIn('user_id', function($query)
{
$query->select('follow_id')
->from('user_follows')
->where('user_id', '1');
})->get();
And finally we can pass in the current user to include the current user's posts:
$twits = Twit::whereIn('user_id', function($query)
{
$query->select('follow_id')
->from('user_follows')
->where('user_id', '1');
})->orWhere('user_id', '1')->get();
Now you should be returned a Collection of twits from the current user and all the users that the current user follows.
And finally you would just replace the 1 with Auth::user()->id to find the current user.
Hope that helps! :)

Resources