Laravel collection pluck method not working as expected - laravel

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 ;))

Related

How to do a surgery on an Eloquent Query on-the-fly

is there any way to interrupt and change the components and scopes of an eloquent query?
in order to add multitenancy to an existing project, I've added a global scope to my models, filtering results by tenant_id. and it works fine.
the problem is I have found more than 500 hardcoded 'where conditions ' and 'create statements' all over the place. such as these:
$notification_type= NotificationTypes::where('id', '2')->get();
or
$tickets = Tickets::create( $title, $body, $sender, '1'); // 1 as statusID
etc.
It's a problem because I'm using the single DB approach for multitenancy and these IDs must be relative to the tenant. for Example in the first query above, I don't want the 'NotificationTypes' with Id of '2', But I want the 'second' NotificationType with that tenant_id (id of this column could be 4 or 7 or else).
I can figure out a way to properly calculate the exact amount of these Relative IDs. but is there any way to find the prior scope and conditions of an eloquent object and change them?
I'm looking for a way to add multitenancy to the project without changing much of the existing code. like a module or plugin.
You can use scope in Laravel
public function scopeActive($query)
{
$query->where('active', 1);
}
Then in your controller just call
$notification_type = NotificationTypes::active()->get();
More ref on doc: https://laravel.com/docs/9.x/eloquent#query-scopes
Dynamic Scope
public function scopeActive($query, $value)
{
return $query->where('active', $value);
}
$notification_type = NotificationTypes::active(value_you_want)->get();
Change value_you_want with what you want
Or you can use
$notification_type = NotificationTypes::where('id', 1)->orWhere('tenant_id', 1)->get();

Laravel - Delete and save in one query

In my app users can vote on submissions.
I first run a delete query for that submission to prevent any duplicate votes, then I save the vote:
SubmissionVote::where('submission_id', $submission->id)->where('user_id', Auth::user()->id)->delete();
$submissionVote = new SubmissionVote;
$submissionVote->submission_id = $submission->id;
$submissionVote->user_id = Auth::user()->id;
$submissionVote->vote = $vote;
$submissionVote->save();
Would it be possible to write this as one query to minimize overall database queries?
Or perhaps there's a way in the table migration to make it so that every submission_id can only have unique user_ids?
You could do this in a neater way like so:
Create a proper relation on the User model:
// models/User.php
public function submissionVote()
{
return $this->belongsTo(SubmissionVote::class);
}
then simply use this single statement in your controller:
Auth::user()->submissionVote()->updateOrCreate([
'vote' => $vote,
]);
This automatically updates the current user's submission OR creates a new one if it doesnt exist yet. Note that you have to use ->submissionVote() (query instance) vs ->submissionVote (model instance) so that you can use the query functions like updateOrCreate(). There are others available like firstOrCreate() or firstOrNew() which do slightly different things but are extremely handy shortcuts.
See https://laravel.com/docs/8.x/eloquent#upserts for more information.
Yes, it perhaps.
Schema::create('submission_votes', function (Blueprint $table) {
$table->unique([
'submission_id',
'user_id',
]);
});
And, update existing model:
SubmissionVote::query()->updateOrInsert(
['user_id' => Auth::user()->id, 'submission_id' => $submission->id],
['vote' => $vote]
);

Get specific values from controller function

I started learning Laravel and I am trying to achieve the following:
Get data from database and display specific field.
Here is my code in the controller:
public function show()
{
$students = DB::select('select * from students', [1]);
return $students;
}
Here is my route code:
Route::get('', "StudentController#show");
That all works for me and I get the following displayed:
[{"id":1,"firstname":"StudentFirstName","lastname":"StudentLastName"}]
How can I get only the "lastname" field displayed?
Thanks in advance!
DB::select('select * from students')
is a raw query that returns an array of stdClass objects, meaning you have to loop through the array and access properties:
$students[0]->lastname
You can also use the query builder to return a collection of objects:
$collection = DB::table('students')->get();
$student = $collection->first();
$student->lastname;
Lastly, using the query builder, you can use pluck or value to get just the last name. If you only have one user, you can use value to just get the first value of a field:
DB::table('students')->where('id', 1)->value('lastname');
I strongly advise you to read the Database section of the Laravel docs.
$students[0]['lastname'] will return the last name field, the [0] will get the first student in the array.
I would recommend creating a model for Students, which would make your controller something like this:
$student = Students::first(); // to get first student
$student->lastname; // get last names
If you only want the one column returned, you can use pluck()
public function show()
{
$last_names= DB::table('students')->pluck('lastname');
return $last_names;
}
This will return an array of all the students' lastname values.
If you want just one, you can access it with $last_names[0]
As a side note, your show() method usually takes a parameter to identify which student you want to show. This would most likely be the student's id.
There are several ways you can accomplish this task. Firstly, I advise you to use the model of your table (probably Students, in your case).
Thus, for example,to view this in the controller itself, you can do something like this using dd helper:
$student = Students::find(1);
dd($student->lastname);
or, using pluck method
$students = Students::all()->pluck('lastname');
foreach($students as $lastName) {
echo $lastName;
}
or, using selects
$students = DB::table('students')->select('lastname');
dd($students);
Anyway, what I want to say is that there are several ways of doing this, you just need to clarify if you want to debug the controller, display on the blade...
I hope this helps, regards!

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.

Attaching new relation and returning the model

I have a User that has many Positions. I want to update the User (the model and the relation Position), and then return the updated result.
The input will be an array of the format
{
first_name,
last_name,
email,
... (other user information),
positions : [
{
id,
name,
descriton
},
...
]
}
My update function currently is
public function update($id)
{
// This is a validation that works fine
if ( ! User::isValid(Input::all())) return $this->withValidation(User::$errors);
$user = User::with('positions')->find($id);
$new_ids = array_pluck(Input::get('positions'), 'id');
$user->positions()->sync($new_ids);
$user->update(Input::all());
return $user;
}
My User and its permissions are updated, but I still get the old $user's relationship back (i.e. the basic information is updated, and the new positions are updated in the DB, but the returned result is the NEW basic information with the OLD positions).
Right now to fix this, I recall the User::with('positions')->find($id) at the end and return that. But why isn't my code above working?
Correct, sync doesn't update related collection on the parent model.
However you should use $user->load('positions') to reload the relation, it will call only 1 query, without fetching the user again.
Also, you can call load on the Collection:
$user->positions->load('anotherRelation');
because here positions is a Collection, which have load method to lazy load all the related models for each item in the collection.
This would not work, since positions() returns relation object, not collection:
$user->positions()->load('anotherRelation');

Resources