Laravel - Delete and save in one query - laravel

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

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

n-n relation in Backpackforlaravel: doesn't delete corresponding items

I'm having troubles with a n-n relationship in my Backpackforlaravel app. I have Registrations and Sessions, the setup in the model looks like this:
Registration:
public function sessions()
{
return $this->belongsToMany(\App\Models\Session::class);
}
Session:
public function registrations()
{
return $this->belongsToMany(\App\Models\Registration::class);
}
So the setup seems to be fine, and I also have a registration_session table in my database.
What I want to achieve is, that whenever a Session gets deleted, I want to delete all the entries in the registration_session table, but also all the Registrations. I thought the deletion of the entries in the registration_session table maybe is a standard, but it didn't delete them when I deleted a session. In order to achieve both, I did the following in the destroy function of my SessionCrudController:
public function destroy($id)
{
$this->crud->hasAccessOrFail('delete');
foreach ($this->crud->getCurrentEntry()->registrations as $registration) {
$registration->delete();
}
DB::table('registration_session')->where('session_id', $id)->delete(); //really required?
return $this->crud->delete($id);
}
I have the feeling that I'm doing things way more complicated than I should, so I would appreciate any recommendations.
Update: this is how I store the data initially in the table...the information is coming from a form, so it's not added in a CRUD panel:
DB::table('registration_session')->insert([
'registration_id' => $reg->id,
'session_id' => $sesId
]);
By default, Backpack or Laravel don't concern themselves with deleting related entries, because they assume your Database layer will handle it. With most DBMSs you can specify such rules, and Laravel makes it easy to do so in their migrations. Take a look at foreign key constraints in the Laravel docs, it's pretty easy to build your migration in such a way that any time one item gets deleted, the related items will to, using "cascade":
$table->foreignId('user_id')
->constrained()
->onUpdate('cascade')
->onDelete('cascade');

Fetching data from multiple tables in laravel5

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.

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

Eloquent Ordering Related Data

I am using Eloquent Repository to get a 'menu' by ID, and return all the associated 'menuitems' along with it. This is working fine, but I am having an issue reordering the 'menuitems' by one of their fields. So I am currently doing:
$menu = $this->menuRepo->getById($id, 'menuitems');
which calls this function within the Eloquent Repo:
public function getById($id, $with = false)
{
if ($with)
{
return $this->model->withTrashed()->with($with)->findOrFail($id);
}
return $this->model->withTrashed()->findOrFail($id);
}
That function is being used throughout the system, so ideally I want to leave that as it is - or would need to change it so that it would not break in all the current usages. But even so, when I tried to add a
->orderBy('name')
within there it applies to 'menu' and not 'menuitems'.
Your with($with) needs to be rewritten, so that it uses relationship constraints:
...->with($with => function($query){
$query->orderBy('name','asc');
})->...

Resources