How to get the last record using Eloquent scope? - laravel-5

I have a Posts table structured like this:
Now I'm trying to get the latest post which is obviously the record with id = 2.
I have this in my theme page posts.htm:
[builderList postLatest]
modelClass = "Me\Articles\Models\Posts"
scope = "scopeLatest"
scopeValue = "{{ :scope }}"
displayColumn = "title"
noRecordsMessage = "No records found"
detailsPage = "-"
detailsUrlParameter = "slug"
pageNumber = "{{ :page }}"
in my model Posts I have:
public function scopeLatest($query)
{
return $query->orderBy('created_at','desc')->first();
}
But this one is returning both records. I also tried using latest()
return $query->latest();
And this one gives me the error:
Maximum function nesting level of '1000' reached, aborting!
Even tried passing parameter like latest('created_at') but got the same error.
Tried dumping dd($query->orderBy('created_at','desc')->first()->toSql()) and got select * from posts where posts.deleted_at is null
Why first() is not limiting to 1 record?
Why latest() is throwing me Maximum error? Is this version specific bug? It seems I cant find documentation related to it. I have Laravel 5.5.48.
I'm not sure now what can I use. Maybe I'm doing something wrong here. I just need to get the latest post.

The solution to your problem is using the ->take(1) method. There are more methods applicable to queries but here is the list of available methods you can run on a collection instance. I believe Builder expects scopes to return a query {[a collection of models]}. I took some time to do testing and in my opinion this might just be a flaw in the Builder Plugin construction.
Now before you look at my research below. I would say you should learn to build your own components like Hardik suggests in a comment. Hope this all helps.
So here is a scope I have for a plugin:
public function scopeFilterTypes($query) {
return $query->whereNotIn('slug', [
'range',
'close',
'all',
'single',
'three',
'five',
'self'
])->first();
}
Here is a picture of my backend form which correctly only sees one item in the relationship (this is why I am using a scope):
Now look at this image of my builder component You can see that it even ignores my ->whereNotIn([]) method on the query:
To prove my finding on this flaw in the builder component I do a find($id) method which would return a model instance not a collection of models. Using the find($id) method also gives me the correct result in the backend form but in the Builder list shows me all the records which is incorrect.
However when I change the scope to ->take(1):
public function scopeFilterTypes($query) {
return $query->whereNotIn('slug', [
'range',
'close',
'all',
'single',
'three',
'five',
'self'
])->orderBy('created_at','desc')->take(1);
}
I get this in the backend form; working correctly:
And I get this from the builder list:

Related

laravel eloquent 'has' method behaves in an unexpected way

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.

Laravel eloquent query with sum of related table

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

Update Table Using Laravel Model

I've got a table for a sports team. The record shows the team selection and some other information. I want to update the record with the team selection. My model is thus:
class Selection extends Model {
protected $table = "selection";
protected $fillable = [
'loose',
'hooker',
'tight',
'secrow1',
'secrow2',
'blindflank',
'openflank',
'eight',
'scrum',
'fly',
'leftwing',
'rightwing',
'fullback',
'sub1',
'sub2',
'sub3',
'sub4',
'sub5'
];
}
So I have a form which gives all the data for the positions and gives the id for the record in the DB. In my controller, I've got:
public function storeFirstTeam()
{
$input = Request::all();
Selection::update($input->id,$input);
return redirect('first-team');
}
But I get the following error:
Non-static method Illuminate\Database\Eloquent\Model::update() should not be called statically, assuming $this from incompatible context
Can anyone point out my silly error?
Please check the code below and this would solve your problem:
Selection::whereId($id)->update($request->all());
The error message tells you everything you know: you’re trying to call a method statically (using the double colons) that isn’t meant to be.
The update() method is meant to be called on a model instance, so first you need to retrieve one:
$selection = Selection::find($id);
You can then can the update() method on that:
$selection->update($request->all());
You should write it like given example below:
Selection::where('id', $input['id'])->update($input);
// Or use this using dynamic where
Selection::whereId($input['id'])->update($input);
Alternatively, you may write it like this as well:
Selection::find($input['id'])->fill($input)->save();
You can also simply update the fields manually:
Selection::whereId($id)->update($request->all());
it is possible to update with primary key but in my case I dont have id field in the detail table. To do it just run a query like this:
DB::table("shop_menu_detail")
->where(['name' => 'old name', 'language' => 'english'])
->update(['name' => 'new name']);
where is used as it is.

Laravel collection pluck method not working as expected

I've entered the fantastic world of Laravel and I am currently looking into seeding a database with fake data for testing.
I have a couple of tables I want to work with; projects and stories.
The stories table has the columns; id, name and project_id (which is a fk to the projects table).
My projects table is already populated with a list of 10 projects. Now I need to populate 100 stories with random projects associated. I have the approach below.
public function run()
{
DB::table('stories')->delete();
DB::statement('ALTER TABLE stories AUTO_INCREMENT = 1');
$faker = Faker::create();
foreach(range(1, 100) as $index)
{
Story::create([
'reference' => $faker->numberBetween(1, 9999),
'name' => $faker->sentence(6),
'project_id' => Project::orderBy(\DB::raw('RAND()'))->get()->first()->pluck('id')
]);
}
}
I don't know if this is the best way of doing what I need. However, when performing this code every story's project_id is set to 1; the first project's id.
When I perform the following command in tinker... It always returns 1 as the id.
Project::orderBy(\DB::raw('RAND()'))->get()->first()->pluck('id')
But when I perform the next command in tinker...
Project::orderBy(\DB::raw('RAND()'))->get()->first()
It returns a random project every time. Which is strange. Because if everything up to ->pluck() is working then pluck() should fetch that collected items id... Right? This is what the above command returns.
<App\Project #000000000c385908000000000de30942> {
id: 6,
name: "New Bernadetteton",
cover_photo_url: "/uploads/covers/horizon-grass.png",
created_at: "2015-07-08 16:32:15",
updated_at: "2015-07-08 16:32:15" }
See below screenshot for my terminal window to illustrate what I mean.
Here's what's happening:
With ->first() you get the actual project model
Then you call pluck('id') on it. BUT the Model class doesn't have that method.
So with, as with every method the Model doesn't know, it redirects the call to a new query builder instance of the model.
In the end, that call ends up here:
Illuminate\Database\Eloquent\Builder#value
public function value($column)
{
$result = $this->first(array($column));
if ($result) return $result->{$column};
}
As you can see, that method runs a new query, using first() and then returns the desired row.
Now what you actually want is either:
1. Don't use pluck at all
There isn't really a need to use that method, you can just access the model property:
'project_id' => Project::orderBy(\DB::raw('RAND()'))->first()->id
2. Use pluck, but do it right
'project_id' => Project::orderBy(\DB::raw('RAND()'))->pluck('id')
And btw, the main method is called value(). pluck() is just an alias. I recommend using value() in new code. It's possible that the alias will be removed some day. (Obviously just in a new release and with a note in the upgrade guide, so don't panic ;))

Why does Eloquent change relationship names on toArray call?

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.

Resources