I'm trying to connect profiles with groups using a pivot table. What I want is to use tinker to add students profiles in study groups.
I don't know what I'm doing wrong but I can't make tinker write to the DB (I know how to do it with OneToOne and oneToMany relationships).
My Profile model has the following function :
public function isStudentInGroup()
{
return $this->belongsToMany(Group::class);
}
My Group model has the following function :
public function hasStudents()
{
return $this->belongsToMany(Profile::class);
}
In tinker, I start by grabbing a Profile and a group :
$profile = Profile::find(1);
$group = Group::find(1);
Then I want to add the group to the isStudentInGroup collection. How do I do it? Here's what I've tried until now. No matter what I do, everytime I use fresh() I see an empty collection.
$profile->isStudentInGroup->save($group);
$profile->isStudentInGroup->save($group->id);
$profile->isStudentInGroup->attach($group->id);
Profile::find(1)->isStudentInGroup->push(1)->update();
Profile::find(1)->isStudentInGroup->push(1)->save();
These all give BadMethodCallException errors.
A promising syntax was this one :
$profile->isStudentInGroup->push($group)
=> Illuminate\Database\Eloquent\Collection {#4646
all: [
App\Models\Group {#5012
id: "1",
start_time: "2022-09-05 18:10:00",
end_time: "2022-09-05 19:00:00",
created_at: null,
updated_at: null,
pivot: Illuminate\Database\Eloquent\Relations\Pivot {#5020
profile_id: "1",
group_id: "1",
},
},
],
}
But then, if I tried to $profile->save(); or $profile->update(); I would get the following
BadMethodCallException with message 'Method Illuminate\Database\Eloquent\Collection::update does not exist.'
What am I missing?
Thanks in advance.
EDIT :
Thanks to matiaslauriti I got the syntax right:
$profile->isStudentInGroup()->save($group);
I also went fishing for knowledge and found this related question with a helpful answer from McHobbes that helped me understand the logic behind the parentheses.
Why eloquent model relationships do not use parenthesis and how do they work?
Pardon me if I did not understand, but you want to make a relation between those relationships, so you have to run:
$profile->isStudentInGroup()->save($group->id);
That should trigger storing that group inside that relationship. If it does not work, I think this will should (or may also work):
$profile->isStudentInGroup()->attach($group->id);
But you must use the relationship, not the Collection. You are mixing relationships objects with Collections, that is why the error says:
Method Illuminate\Database\Eloquent\Collection::update does not exist.
A Collection does not have update or save as it is a collection, it can't be saved anywhere, but a relationship can.
Try this
App\Models\Profile::find(1)->isStudentInGroup;
i hope it was useful .
Related
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();
I want to get the collection of a Section model if it has at least one User. from the docs the has() method does this, great. The collection retrieved does not have the the users relationship in it. Yet when I loop through the collection , I can get the properties of the users. Why?
class Section extends Model
{
protected $guarded = [];
public function users()
{
return $this->hasMany('App\User');
}
}
class User extends Authenticatable
{
protected $guarded = [];
public function section()
{
return $this->belongsTo('App\Section');
}
}
what I did is this:
$section = Section::where('id' , 1)->has('users')->get();
the collection is this:
Illuminate\Database\Eloquent\Collection {#3025
all: [
App\Section {#3015
id: 1,
class_id: 1,
section_name: "A",
created_at: "2019-12-14 18:26:01",
updated_at: "2019-12-14 18:26:01",
},
],
}
Now the weird part is that when I do the following, it gives the properties of users even though in the collection the users relationship is not present.
Why?
#foreach ($section as $section)
#foreach ($section->users as $student)
<p>{{$student->name}}</p>
#endforeach
#endforeach
solomon
uche
kene
This is just how Laravel works.
Accessing $model->{relationship}, in your case $section->users, is a magic function that checks if you have explicitly loaded the relationship via something like Section::with('users'), and if you haven't, loads it then.
The reason you're not seeing users when you run dd($section) is that you didn't explicitly load the relationship, but this does not mean that it is unavailable. If you included with('users') in your initial query, you'd see the following:
$section = Section::where('id' , 1)->has('users')->with('users')->get();
App\Section {#3015
id: 1,
class_id: 1,
section_name: "A",
created_at: "2019-12-14 18:26:01",
updated_at: "2019-12-14 18:26:01",
users: [
0 => App\User {#3016}
id: ...
name: ...
]
},
// Or similar
Basically, you weren't loading the relationship, so you aren't able to see it when using dd($section), but it is still available in PHP, due to Laravel's magic methods.
I should note as well, use the correct variable naming and closure (->get(), ->first(), etc) for your query.
$section is a poor name when using ->get(), as you're getting multiple records back from the database. Either use $sections, or change the closure to ->first(), and don't use a foreach() if you use ->first().
Looks like your first echo:
$section = Section::where('id' , 1)->has('users')->get();
Just prints the section where it has a user, but you're not specifically saying give me the users as well.
In the next loop you are looping over each section in the view, and specifically saying loop the relationship. this is visible by this line: #foreach ($section->users as $student)
Im reading the docs here: https://laravel.com/docs/6.x/eloquent-relationships#querying-relations
In your first echo when printing the section you can get the users like so: echo $section-> users()
Ok, now I understand your question.
First
The has method does not mean that it's going to include User. It means that will return all the sections that have at least one user. I think there is/are users for Section id ===1. So, in your code with has or without it, it does not make any difference.
if you want to load a relationship explicit you should use with
Section::where('id' , 1)->with('users')->get();. Then you should have users collection under per section.
Second
The reason why you can still access the user properties in your blade file, is because of lazy loading. even it's not included in the original DB query and result but when you are trying to access it laravel still tries to fetch those for you. And this may cause N+1 problem.
I have a table users and posts with columns user_id and post_views.
In post_views I keep information how many times post was display.
And now, in query I would like to get user with sum of post_views all his posts.
I tried do something like this:
User::where(['id'=>$id])->with('posts')->get();
And in model I defined:
public function posts()
{
return $this->hasMany('App\Models\Post')->sum('post_views','AS','totalViews');
}
But without success.
How to do it?
Thank you
You can use a modified withCount():
public function posts()
{
return $this->hasMany('App\Models\Post');
}
$user = User::withCount(['posts as post_views' => function($query) {
$query->select(DB::raw('sum(post_views)'));
}])->find($id);
// $user->post_views
You can use
User::withCount('posts')->find($id)
to get the user with the id $id and a posts_count attribute in the response
I'm not fully sure what the intention of ->sum('game_plays','AS','totalVies'); is - you would need to add more context if you want this
Just something to add with regards to your shown code: No need to query by id using where + the get() at the end will make you query for a collection. If you want to get a single result use find when searching by id
As always laravel has a method for that : withSum (Since Laravel v8)
Note : I know that at the time of the message was posted, the method did not exist, but since I came across this page when I was looking for the same result, I though it might be interesting to share.
https://laravel.com/docs/9.x/eloquent-relationships#other-aggregate-functions
In your case it should be :
$user = User::withSum('posts as total_views', 'post_views')->find($id);
Then you can access to the result :
$user->total_views
I have two tables posts and sharedposts. sharedposts table has columns "user_id", "updated_at" and "post_id" where as posts table has 'id', 'title' and 'body'. I know about the relations to link both tables. All i want to do is to display each post ordered by 'updated_at' from both the tables. Please help with both query builder and eloquent way. Thanku
The code is not tested, but you can try this:
Post::whereHas('sharedpost',function($query){
$query->orderBy('updated_at');
})->get()
Maybe a bit different approach if there won't be to much of the updates of the models? This way I suppose the read should be faster (somebody please correct me if I'm wrong here)
Add another field to the table by creating new migration and adding
$table->timestamp('last_updated');
Then using Events update the field whenever the Post or SharedPost is updated by adding this to AppServiceProvider:
public function boot()
{
Post::saving(function ($post) {
$post->last_updated = Carbon::now();
});
}
I'm not sure if this will work though and it would be appreciated if you returned a feedback as I'm lazy to try it out myself at the moment :). Worse case scenario you might need to change the method as:
public function boot()
{
SharedPost::saved(function ($sharedpost) {
$sharedpost->post->last_updated = Carbon::now();
$sharedpost->save();
});
}
or you could check out Observers if you don't want the code in the AppServiceProvider. Haven't used it but it seems straightforward.
Then you could simply Post::orderBy('last_updated')->get();
This way you will have one or two more inputs to your database but I think (and guys please correct me if I'm wrong) the get() should be faster. Keep in mind I'm far from a MySQL expert.
And in the end, you can make it even faster by saving this data to Cache using Redis.
I'm encountering an annoying problem with Laravel and I'm hoping someone knows a way to override it...
This is for a system that allows sales reps to see inventory in their territories. I'm building an editor to allow our sales manager to go in and update the store ACL so he can manage his reps.
I have two related models:
class Store extends Eloquent {
public function StoreACLEntries()
{
return $this->hasMany("StoreACLEntry", "store_id");
}
}
class StoreACLEntry extends Eloquent {
public function Store()
{
return $this->belongsTo("Store");
}
}
The idea here is that a Store can have many entries in the ACL table.
The problem is this: I built a page which interacts with the server via AJAX. The manager can search in a variety of different ways and see the stores and the current restrictions for each from the ACL. My controller performs the search and returns the data (via AJAX) like this:
$stores = Store::where("searchCondition", "=", "whatever")
->with("StoreACLEntries")
->get();
return Response::json(array('stores' => $stores->toArray()));
The response that the client receives looks like this:
{
id: "some ID value",
store_ac_lentries: [
created_at: "2014-10-14 08:13:20"
field: "salesrep"
id: "1"
store_id: "5152-USA"
updated_at: "2014-10-14 08:13:20"
value: "salesrep ID value"
]
}
The problem is with the way the StoreACLEntries name is mutilated: it becomes store_ac_lentries. I've done a little digging and discovered it's the toArray method that's inserting those funky underscores.
So I have two questions: "why?" and "how do I stop it from doing that?"
It has something in common with automatic changing camelCase into snake_case. You should try to change your function name from StoreACLEntries to storeaclentries (lowercase) to remove this effect.