I'm very much a beginner when it comes to database relationships hence what I suspect is a basic question! I have two database tables as follows:
Projects
id
company_id
name
etc...
rfis
id
project_id (foreign key is id on the Projects table above)
Number (this is the column I need help with - more below)
question
The relationships at the Model level for these tables are as follows:
Project
public function rfi()
{
return $this->hasMany('App\Rfi');
}
RFI
public function project()
{
return $this->belongsTo('App\Project');
}
What I'm trying to achieve
In the RFI table I need a system generated number or essentially a count of RFI's. Where I'm finding the difficulty is that I need the RFI number/count to start again for each project. To clarify, please see the RFI table below which I have manually created with the the 'number' how I would like it displayed (notice it resets for each new project and the count starts from there).
Any assistance would be much appreciated!
Todd
So the number field depends on the number of project_id in the RFI table. It is exactly the number of rows with project_id plus one.
So when you want to insert a new row, you calculate number based on project_id and assign it.
RFI::create([
'project_id' => $project_id,
'number' => RFI::where('project_id', $project_id)->count() + 1,
...
]);
What I understood is that you want to set the value of the "number" field to "1" if it's a new project and "increment" if it's an existing project. And you want to automate this without checking for it every time you save a new row for "RFI" table.
What you need is a mutator. It's basically a method that you will write inside the desired Model class and there you will write your own logic for saving data. Laravel will run that function automatically every time you save something. Here you will learn more about mutators.
Use this method inside the "RFI" model class.
public function setNumberAttribute($value)
{
if(this is new project)
$this->attributes['number'] = 1;
else
$this->attributes['number']++;
}
Bonus topic: while talking about mutators, there's also another type of method called accessor. It does the same thing as mutators do, but just the opposite. Mutators get called while saving data, accessors get called while fetching data.
Related
I have a feed table that contains id, body, created_at fields. When I send Post() on postman after Delete() method the id for the feed table auto_increments as if a record has not been deleted. I am unsure how to rectify this, I am using MySql database, nestjs and TypeORM for the backend.
feed controller.ts
#Controller("feed")
export class FeedController {
constructor(private feedService: FeedService) {}
#Post()
createNewPost(#Body() feedPost: HomeFeedDto): Observable<HomeFeedDto> {
return this.feedService.createPost(feedPost);
}
#Get()
allPosts(): Observable<HomeFeedDto[]> {
return this.feedService.getAllPosts();
}
//api delete method
#Delete(":id")
// delete home feed post by id
deleteFeedPost(#Param("id") id: number): Observable<DeleteResult> {
return this.feedService.deletePost(id);
}
}
This is just the way that auto incrementing columns work in a database. Once a record has been created that uses a particular id value it can never be used again, even if the record that owned it was deleted.
What would you expect to happen in the case where there were many records? If the current incrementing id was 1000 and then you deleted the record with id = 1 would you expect that the next time you inserted a record it would be given id = 1 again instead of id = 1001?
There are lots of practical reasons why re-using a previously issued id would be very bad for business logic especially if anyone who is a consumer of your API has a cached version of the old record.
If you really want to achieve this behavior you would have to look at writing custom functions either inside of the database or your API which check to see if any ids are missing from sequence and then manually assign your own IDs instead of letting the database do it. I would highly recommend you don't do this though as the behavior you're seeing is designed like that for a reason.
My models have both id and counter attributes. The id is a UUID, and the counter is an integer which is auto-incremented by the database.
Both are unique however I rely on id as the primary key. The counter is just a human-friendly name that I sometimes display to the user.
Immediately before an object is created a listener gives it a UUID. This works fine.
When the record is saved, MySQL increments the counter field. This works fine except that the copy of the object which I have in memory does not have the counter value. I can reload the object to find out what its counter is, but that would require another database query.
Is there a way to find the value of the counter without a specific database query? For example, is it returned as part of the response from the database when a record is created?
Few things:
Use create(array $attributes) and you'll get exactly what you want. For this having right, you have to ensure that $fillable array consists all attributes' names passed to create method.
You should use Observer on model instead of listener (most likely creating method).
Personal preference using Eloquent is that you should use id for id (increment field) and forget custom settings between models because by default it is what relations expect and so on
public function secondModels()
{
return $this->hasMany(SecondModel::class);
}
is pretty much no brainer. But for having this working best way would be (also following recommendations of this guy) FirstModel::id, SecondModel::id, SecondModel::first_model_id; first_models, second_models as table names. Avoiding and/or skipping this kind of unification is lot of custom job afterward. I don't say it can't be done but it is lot of non-first-time-successful work done.
Also, if you want visitor to get something other than id field name, you can make computed field with accessor:
/**
* Get the user's counter.
*
* #return string
*/
public function getCounterAttribute(): string
{
return (string)$this->id;
}
Which you call then with $user->counter.
Also personal preference of mine is to have most possible descriptive variable names so uuid field of mine would be something like
$table->uuid('uuid4');
This is some good and easy to make practice of Eloquent use.
Saying all this let me just to say that create() and save() will return created object from database while insert() shall not do it.
I use laravel framework . I have two table. users and points . that has one to many relationship between them.in points table saved many records related to a one user that keep all of the ponits we assign to this user.I want to keep sum this points to one field as sumpoints filed in user table.how can i do it in laravel
Do something like this in your Points model:
public static function boot()
{
parent::boot();
self::created(function($model){
$user = Auth::user();
$user->sumfields = Points::where('user_id',$user->id)->sum('points');
$user->save();
});
}
The self::created function gets called after a new instance of that model has been created.
However if this was my project, I would probably do what Ivan Jelev suggested to you (doing the sum only when you need it).
I have two models with a simple 1-n relation (category and item with category_id). I would like to show how much items there is in the category. I would like to have the number cached rather then always doing the count, so I have an extra field in category table called "total_items_count".
How to best do this total count triggering, when to call the "countUpdate" function since the relation could change from several places (backend, api, frontend...).
My initial plan was to use AFTER_UPDATE event, but "link()" must be called after the item is stored in database (on adding new item at least) so then I do not know which category is the item related to. I also need to know the old category, in case item goes from one category to another. In the backend controller I am using $item->link('category', $categoryObj); as I might change this relation to n-n someday.
Any advice on how to have complete control if the link between item and category changes and then update the count for the old and new linked category?
Thanks
The first and most important thing you need to now, is that you need to update the category count field via the ActiveRecord::updateCounters() method, in order to prevent incorrect update from multiple places. So you can do one of those things:
In the category item model you can do the following:
public function save($runValidation = true, $attributeNames = null)
{
if (parent::save(runValidation, attributeNames)) {
$this->category->updateCounters(['total_items_count' => 1]);
return true;
}
return false;
}
Or you can write a centralized component (let's say app\components\Category) which you will call from the whole application. This component will have a method that saves an item. For example:
public function saveItem($category, $item)
{
if ($item->save()) {
$item->link('category', $category);
$category->updateCounters(['total_items_count' => 1]);
return true;
}
return false;
}
Of course, when you delete an item, you will call:
$category->updateCounters(['total_items_count' => -1]);
See the ActiveRecord::updateCounters() API
From the Yii2 documentation:
Note: If you use yii\db\ActiveRecord::save() to update a counter column, you may end up with inaccurate result, because it is likely the same counter is being saved by multiple requests which read and write the same counter value.
If you need to keep track of which is the old category, just create another column let's say "old_category" to the category item table and when you change the category of the item put the old category id there, and the new category id to the category_id column.
Does that helps? If you have any questions, please ask!
I'm trying to output the filter results with only matching elements.
I have two tables (in the real project, which will be 5), let's say companies and projects. A company may have more than one project or may not have any.
These are the relations:
/app/models/Company.php
<?php
class Company extends Eloquent {
public function projects() {
return $this->hasMany('Project','companyID');
}
protected $table = 'companies';
}
/app/models/Project.php
<?php
class Project extends Eloquent {
public function companies() {
return $this->belongsTo('Company','companyID');
}
}
What I want to do is, I want to get results of them both but only with matching parameters.
I've tried this:
return Company::with(array('projects'=>function($query){
$query->where('id',99); //project's id is 99
}))->get();
This is the output JSON
If I change the value from 99 to 1 (there is a result with products.id of 1), it changes into this:
I only want to get the second result from the second JSON i've posted.
As you can see in the second JSON (I'm using this parser to check), all companies are loaded regardless of the project, and only the rows matched have the object projects.
There will be more 'with's and I don't know how to filter only matching elements.
I also tried having() inside closure, but it's still same:
$query->having('projects.id','=',99);
Is there a way to filter only matching results (without using a loop) which the output will only include the results having the matched projects object?
Edit: Actually, 5 tables will be filtered.
companies, projects, works, users and user_works
Let's say;
"Companies" have many projects.
"Projects" have many works
"Works" have many users, also "Users" may have more than one work (pivot table user_works).
All relations are set correctly from models.
I want to do a global searching to these.
Something like: "Bring me the user id 1's works which has company id of 5 and project id of 4", but none of the fields are mandatory.
So these are also valid for searching: "Bring me everyone's works on project id of 2", or "bring me id 2's works", or "bring me all the works starting from today", "bring me the id 1's works on project 2", "Bring me this year's works done of company id 1".
Thanks in advance
Using whereHas() is the solution on my case. It filters relation and affects the results returned in the main query.
If you have the id of the project, would it make more sense to go that route? $project = Project::find(99); and then the company variables would be accessible with $project->companies->name;
It would make more sense to rename the companies() function to company() because a project will only ever belong to one.