How to create a hasMany relationship with different models in folder? - laravel

I have a strange Laravel 9 setup due to being constrained to a very ancient database.
I'm trying to come up with a clean way to create a hasMany relationship to multiple models located in a folder. I believe it would be easiest explained with diagrams:
app/Models/
- Customer
app/Models/Records/
- Orange
- Green
- Blue
Now, all of these records has some connection to customer, but these are all different as well, for example, orange refers to customer using cid, where Green might use customerid.
I've already set up logic where every record model has a customer belongsTo relationship depending on the different field names.
public function customer()
{
return $this->belongsTo('App\Models\Customer', 'CustomerId');
}
I need to create a records() function in the Customer model, that pulls in all of these Records where found. I can't create any new tables that would be stored on the DB.

For multiple tables (models) to be connected as hasMany is probably not possible out of the box in Laravel.
However if you do really need to combine them all, I did once but it is not pretty.
First add following to each of your App\Records\Model in example is Green
class Green extends Model
{
protected $table = 'GreenRecords';
....
....
// this is to append/add extra fields into your model
// that not exist inside the table it self
protected $appends = [
'product_data',
'stuffs',
];
// relationship to App\Models\Customer
public function customer()
{
return $this->belongsTo(Customer::class, 'customerid', 'id');
}
// you can use it later as Green::query()->customer($id)->get()
public function scopeCustomer($query, $id)
{
return $query->where('customerid', $id);
}
// you can add here as many data as you like from table GreenRecords,
// this will make Green Orange and Blue as if they have common fields
// you can also separate them by adding add each custom field to $appends
public function getProductDataAttribute()
{
return [
'name' => $this->name,
'color' => $this->color,
'stuffs' => $this->stuffs,
'other_stuffs' => $this->other_stuffs,
];
}
public function getStuffsAttribute()
{
return $this->stuffs;
}
}
And now for the Customer model
class Customer extends Model
{
// your Customer relation to Orange
public function oranges()
{
return $this->hasMany(Orange::class, 'cid', 'id');
}
// your Customer relation to Green
public function greens()
{
return $this->hasMany(Green::class, 'customerid', 'id');
}
public function getRecords(): Collection
{
// we can not use collection merge due to same 'id' in Orange and Green
$collections = collect();
if ($oranges = Orange::query()
->customer($this->id)
->get()
) {
foreach ($oranges as $record) {
$collections->push($record);
}
}
if ($greens = Green::query()
->customer($this->id)
->get()
) {
foreach ($greens as $record) {
$collections->push($record);
}
}
return $collections;
}
}
So now you can do
$customer = Customer::find(3);
$records = $customer->getRecords(); // result will be a collection
And inside your blade you can access them
#foreach ($records as $record)
{{ $record->stuffs }}
{{ $records->product_data['other_stuffs'] }}
#endforeach
A collection can be filtered and sorted
$records->where('product_data.stuffs', 'abc')->sortBy('name');
The only problem here is the records id where Orange can have the same id as Green and Blue.
Best is to add new field into Orange Green and Blue
$table->uuid('record_id')->nullable();
Hope this can help you out.

None of these solutions quite achieved what I was looking for in terms of simplicity, and in my case my database is quite out of date and slow, so I ended up landing on a solution that is quite simple and faster than everything posted for my use case:
public function records()
{
return [
"Orange" => $this->hasMany(\App\Models\Records\Orange::class, 'CustomerId', 'Id')->get(),
"Blue" => $this->hasMany(\App\Models\Records\Blue::class, 'Customerid', 'Id')->get(),
"Purple" => $this->hasMany(\App\Models\Records\Purple::class, 'customerid', 'Id')->get(),
"Black" => $this->hasMany(\App\Models\Records\Black::class, 'CustomerId', 'Id')->get(),
"Green" => $this->hasMany(\App\Models\Records\Green::class, 'Customerid', 'Id')->get(),
"Cyan" => $this->hasMany(\App\Models\Records\Cyan::class, 'CustomerId', 'Id')->get()
];
}
This achieves what I was looking for in terms of setting up the relationships when the customerId field was subject to change on the other tables, and is pretty readable overall.

Related

How to add to my Index/Show, so that it displays a table, from another table. (Laravel)

First post here, hope all is well with everyone! I am working on my senior project and struggling with this concept. Maybe it isn't possible, or it is and I'm thinking about it the wrong way.
I currently have this- This displays the student, with all its foreign keys in the other tables. In settings, there is a foreign key 'conversion_id.', I would like settings on the student call(code below) to ALSO display the conversion table, from the FK in settings.
$student = Student::with('studentIntroSurveys', 'settings', 'giftsSurveyResults',
'studentGiftSurveys', 'devotionals', )->get();
If this does not make sense, I am sorry. I am still l new to the language.
(Code to display students. It shows settings, but not the conversions within settings.)
$student = Student::with('studentIntroSurveys', 'settings', 'giftsSurveyResults',
'studentGiftSurveys', 'devotionals', )->get();
if (!$student) {
return response('No Data', 400);
} else {
return response($student);
}
I would like it to display this settings, but WITH the conversion_id table!!
What it displays =
settings: { setting_id: 4, student_id: 1, dark_mode: 1, conversion_id: 1,
notification_enabled: 1, notification_time: "08:00:00" } `
my has-one method =
public function settings()
{
$settings = $this->hasOne(Settings::class, 'student_id', 'student_id');
return $settings;
}
You can use the dot syntax for nested eager loading: 'settings.conversation' e.g.
$student = Student::with('studentIntroSurveys', 'settings.conversation', 'giftsSurveyResults', 'studentGiftSurveys', 'devotionals', )->get();
Alternatively, you could set up a belongsToMany relationship on the Student model and use settings as the pivot table:
public function conversations()
{
return $this->belongsToMany(Conversation::class, 'settings')
}
Just a few FYIs:
Your if statement is never going to return the 400 response as $student is always going to be a collection. You could instead do if ($student->isEmpty()).
I would also recommend changing the variable to $students as it will be a collection of students rather than a single student.
You can simple your settings relationship by removing the temporary variable and just returning the relationship:
public function settings()
{
return $this->hasOne(Settings::class, 'student_id', 'student_id');
}

Attach existing record to eloquent hasMany / belongsTo relationship

I have a model SalesArea which hasMany Regions. Regions belongTo one SalesArea.
class SalesArea extends Model
{
public function regions() {
return $this->hasMany('App\Region');
}
}
class Region extends Model
{
public function sales_area() {
return $this->belongsTo('App\SalesArea');
}
}
Due to some changes in organization, the parent (SalesArea) is being created after the Regions have been created. I am trying to associate the Regions with their associated Sales areas when the Sales Area record is created.
I created a migration to add sales_area_id to the regions table, and they are currently all null.
I have a select box that has the various IDs of Regions and I would like to associate the regions with a Sales Area at once with an array of ids. None of the association methods are working.
$area = SalesArea::create($request->all());
$area->regions()->attach([1,2,3]); // BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::attach()
$area->regions()->add([1,2,3]); // BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::add()'
$area->regions()->sync([1,2,3]); // BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::sync()'
$area->regions()->associate([1,2,3]); // BadMethodCallException with message 'Call to undefined method Illuminate\Database\Query\Builder::associate()'
I am stumped on how this is supposed to work. Regions already exist, I just need to add their relationships to their associated sales area.
public function store(Request $request)
{
$this->validate($request, [
'name' => 'required|unique:sales_areas',
'regions' => 'required|array',
'regions.*' => 'int',
]);
$salesArea = SalesArea::create($request->all());
// Because we have a hasMany not a many to many, we have to do it this way
Region::whereIn('id', $request->regions)->update(['sales_area_id' => $salesArea->id]);
flash('Created Sales Area ' . $salesArea->name);
return redirect()->route('areas.index', $salesArea->id);
}
Please ensure you have the column, sales_area_id in your regions table
$salesArea = SalesArea::create($request->all());
$regions = Region::whereIn('id', [1, 2, 3])->get();
$salesArea->regions()->saveMany($regions);
I am not sure, why you are creating the regions this way. If there is a hasMany()relation between SalesArea & Region then all regions should be created like :
$salesArea->regions()->create([ ... ]);
And if your relations are like this
class SalesArea extends Model
{
public function regions() {
return $this->belongsToMany(Region::class)->withPivot(['id'])->withTimestamps();
}
}
class Region extends Model
{
public function sales_area() {
return $this->belongsToMany(SalesArea::class)->withPivot(['id'])->withTimestamps(
}
}
then you can do something like this.
$salesArea = SalesArea::create($request->all());
$regions = Region::whereIn('id', [1, 2, 3])->get();
$salesArea->regions()->sync($regions->pluck('id')->toArray());

how to save multiple dropdownlist yii2

I'm trying to save multiple data from a dropdownlist, I have 2 tables Asistencia and Mecanico in the table Asistencia i have this in the _form
<?php
echo $form->field($model, 'mecanico_id[]')
->dropDownList(ArrayHelper::map(Mecanico::find()->all(), 'id_mecanico', 'nombre'),
[
'multiple'=>'multiple',
'class'=>'chosen-select input-md required',
]
)->label("Mecanicos");
?>
i know if i want to save multiple data i have to change in controllers-> actionCreate/Update but i dont know how. Here is my actionCreate
public function actionCreate()
{
$model = new Asistencia();
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id_asistencia]);
}
return $this->render('create', [
'model' => $model,
]);
}
I need sample controller code explaining how to save multiple items from a drop down list to the database, as well as update the list of saved items. Thanks.
here is my table of Asistencia
Asistencia table
Table of Mecanico
Mecanico table
and the relation of those 2 table
enter image description here
For example you have Product and Category many to many relation.
In your Product model,
Declare field:
public $categories_ids;
Put it in 'safe' in rules():
[['categories_ids'], 'safe']
3.Declare AfterSave function:
public function afterSave($insert, $changedAttributes) {
// If this is not a new record, unlink all records related through relationship 'categories'
if(!$this->isNewRecord) {
// We unlink all related records from the 'categories' relationship.
$this->unlinkAll('categories', true);
// NOTE: because this is a many to many relationship, we send 'true' as second parameter
// so the records in the pivot table are deleted. However on a one to many relationship
// if we send true, this method will delete the records on the related table. Because of this,
// send false on one to many relationships if you don't want the related records deleted.
}
foreach($this->categories_ids as $category_id) {
// Find and link every model from the array of ids we got from the user.
$category = Category::findOne($category_id);
$this->link('categories', $category);
}
parent::afterSave($insert, $changedAttributes);
}
Decare AfterFind function():
public function afterFind(){
parent::afterFind();
$this->categories_ids = ArrayHelper::getColumn($this->categories, 'id');
}
Declare relation:
public function getCategories() {
return $this->hasMany(Category::className(), ['id' => 'category_id'])->viaTable('product_category', ['product_id' => 'id']);
}
I hope it will help to you.

Paginate with Eloquent but without instantiation Models

We have two Models:
SimpleModel (id, country, code)
ComplexRelatedModel (id, name, address)
SimpleModel has many ComplexRelatedModel, then
class Product extends Model
{
protected $fillable = [
'name'
];
/* hasOne */
public function complexRelatedChild()
{
return $this->hasOne(self::class, 'parent_id', 'id');
}
}
If we do
$simples = SimpleModel
->with('complexRelatedChild')
->simplePaginate(100000 /* a lot! */);
And we need only do
foreach ($simples as $simple) {
echo $simple->complexRelatedChild->name;
}
Any ComplexChild has hydratated and ready. This takes a lot of memory in my case. And we need just one field without any funciton or feature of Model.
It's possible use some data field from related object or with eloquent this isn't possible?
Not sure I completely understand your question. You want to only load one field from the complexRelatedChild relation to keep memory limit down?
You could do:
$simples = SimpleModel::with(['complexRelatedChild' => function($query){
return $query->select(['id', 'name']);
})
->simplePaginate(100000);
Which can be simplified to:
$simples = SimpleModel::with('complexRelatedChild:id,name')
->simplePaginate(100000);
However if I were you, I would try to paginate less items than 100000.
Update:
You could use chunk or cursor functions to process small batches of SimpleModel and keep memory limit down.
SimpleModel::chunk(200, function ($simples) {
foreach ($simples as $simple) {
}
});
or
foreach (SimpleModel::cursor() as $simple) {
}
See the documentation for more information

Laravel - defining the right relationship

User Table: has zone_id field in it.
Zones table: has world_id field in it.
Each user can be in one zone, for example, zone_id = 1
Each zone belongs to one world, for example - world_id = 5
My desired output is returning user zone and world info.
This is how I can make it without any relationship set:
$zone = Zone::find($user->zone_Id);
$world = World::find($zone->world_id);
$data = $user;
$data['zone'] = $zone;
$data['zone']['world'] = $world;
My question is.. I'm sure relationship can be used for a cleaner code, but I'm not sure how to set it up.
Should I stick with the current code or define a relationship?
If the answer for 1 is define a relationship, Any help of what's the right relationship between these 3 models?
Solution 1:
`public function getZone(Request $request)
{
$token = $request->input('token');
$user = JWTAuth::toUser($token);
// Simplest example using relationships
$userWithZone = User::with('zone.world')->find($user->id); // You'll get the `zone` relationship info here, too
return $userWithZone;
}`
Error: returns "Call to a member function getQuery() on null"
Here's an example of how you can achieve this with Eloquent Relationships.
// User.php
class User
{
public function zone()
{
return $this->belongsTo(Zone::class);
}
}
// Zone.php
class Zone
{
public function world()
{
return $this->belongsTo(World::class);
}
}
Simplest example using relationships
$user = User::with('zone.world')->find($id); // You'll get the `zone` relationship info here, too
You can get more complex with your relationships if you want
$user = User::with(['zone' => function($query) {
$query->with('world')
->select('id', 'zone_name', 'world_id');
})->select('username', 'zone_id')->find($id);
or even...
$user = User::with(['zone' => function($query) {
$query->with(['world' => function($query2) {
$query2->select('id', 'world_name');
}])->select('id', 'zone_name', 'world_id');
})->select('username', 'zone_id')->find($id);
Your resulting $user will look something like:
'user' => [ // This is a Collection
'username',
'email',
'zone_id',
'zone' => [ // This is a Collection
'id',
'zone_name',
'world_id',
'world' => [ // This is a Collection
'id',
'world_name'
]
]
];

Resources