Laravel/Eloquent get all appointments from a polymorphic "member_id" through a appointment_members table - laravel

I have an appointments table and an appointment_members table and my users need to be able to get a collection of appointments by searching with a "member_id", which could be a Person, Incident or CustomerID. The appointment_members table has member_id and appointment_id columns, so the member_type (also a column) is irrelevant. This all set up by a previous dev and what is missing are the relationships on the Eloquent models. I'm just creating a simple GET route that needs to return any/all appointments by that member_id. Each row has one appointment, so if I were to pass in a member_id that returned 10 results, some could have appts and others not, but at the end of the day I just need a collection of appts that are related to that member_id. Here's a screenshot of the appointment_members table:
If I create a simple hasOne relationship to appointments on appointment_members:
public function appointments()
{
return $this->HasOne(Appointment::class, 'id', 'appointment_id');
}
I can get a collection of appointment_members with it's respective appointment, but not sure how I boil it down to just getting the appointments. One workaround I have is to use that HasOne and then pluck/filter the appointments:
$appointmentMembers = AppointmentMembers::where('member_id', $request->input('memberId'))->get();
$appointments = $appointmentMembers->pluck('appointments')->filter();
Curious if anyone might see a better way to go about this. Thanks!

I'm possibly not understanding, but I would probably take the simplest approach here if the member type is not important.
The table is already set up to handle either a belongsToMany or a morphMany, so create the relationship on the Member class (or if you don't have a parent member class, stick it on each of the types Person, Incident, etc. You can also do this via poly, of course, but this is a simple example to get what you need):
public function appointments()
{
return $this->belongsToMany(Appointment::class)->withPivot('member_type');
}
And then just query on the member object you need appointments for (having poly would make this one step):
$allAppointmentsForID = $member->appointments();
$appointments = $allAppointmentsForID->wherePivot('member_type', $whateverClassThisIS);
The above takes member_type into account. If this doesn't matter, you can just use the top line.
Your original db is setup to handle polymorphic relations, so if you wanted more than the appointment you can set it up this way as well. For now, you'll need to add the TYPE to the query to cover the different classes.
If the member type is important, polymorphic might be something like this on the Member class:
public function appointments()
{
return $this->morphMany(Appointment::class, 'memberableOrmember_typeOrWhatever');
}
Then you can query on the member object with just one line
$appointments = $member->appointments();

Related

laravel - how to deal with model of similar type

I am trying to model a company and its relevant employee strucutre. I have 3 tables (company, position, employee) as below, and company haveMany position, and employee haveMany position. Position belongs to company, and position belongs to employee.
However, different position have some common field like onboard date, but have some fields are different. Forexmaple, CEO has a gurantee employment period, while other position dont. Quite a number of field is different too for different position.
In that case, should I using polymorphic to model? but as the company has quite a number of different position, this will create quite a lot new table in the database.
Do you have any advice on how to model different positions?
Companies
id
Position
Positions
id
type [CEO, manager, director, clerk, etc]
company_id
employee_id
Onboard Date
Ceased Date
Employees
id
position id
In that case, should I using polymorphic to model? but as the company has quite a number of different position, this will create quite a lot new table in the database.
No, why would be?
First of all, it should be manyToMany relation and not oneToMany because if you have two companies both of those can have CEO (for example) position and if you set $position->belongsTo(Company::class); it couldn't work.
It is polymorph relation there with positions as polymorphic angle of that triangle.
You would need
// companies
id
name
// employees
id
name
// positions
id
name
// positionables
position_id
positionable_id
positionable_type
With this, your models would be
class Company extends Model
{
public function positions()
{
return $this->morphToMany(Position::class, 'positionable');
}
}
class Employee extends Model
{
public function positions()
{
return $this->morphToMany(Position::class, 'positionable');
}
}
class Position extends Model
{
public function companies()
{
return $this->morphedByMany(Company::class, 'positionable');
}
public function employees()
{
return $this->morphedByMany(Company::class, 'positionable');
}
}
It allows you to set positions, companies and employees separately. Meaning, From dashboard you can make some new positions that will be available on frontend from select options let's say. Of course you should allow company and to employee to create new position (I suggest) and not just to use existing one but it could be out of scope of this question now: in example, when (and if) company creates new position (instead of selecting existing ones from options list), you would first create that position and store it into positions table and then associate company with it. Also, when using this kind of chained inputs to DB don't forget to use DB transactions. Into positionables table you would set other fields important for each relation (onboard_date, ceased_date, etc).
Documentation is very good and consult it if something is not clear (I hope it is already).
Disclaimer: I don't know rest of your project business plan and rest of project's requirements but for these three entities this is the best structure you can go with. I have set just mandatory members to models and tables for this example. Also in offered answer, I presumed use of Laravel's naming convention that's blindly followd from docs and this repo.
If the fields have no relationship with other tables, one possible way is to have a key-value table to store those fields and values:
position_fields
- id
- position_id
- key
- value
You can hence store the fields in key and the respective value in value. Then you may overwrite the __get magic method in Position model e.g.
public function __get($key){
$position_field = $this->hasMany(PositionField::class)->where('key', $field)->first();
return !!$position_field ? $position_field->value : $this->getAttribute($key);
}

Laravel Get previous records by date (optimization)

I have a bookings table with fields:
name | String
from | Datetime
to | Datetime
I select some of those bookings and display them as a list. I also check if the previous booking is less than 30 days apart.
I do this by querying for each booking the previous booking:
#if ($booking->previousBooking()) // Simplified version but you get the idea
The underlying code:
public function previousBooking()
{
return Booking::where('from', '<', $this->from)
->orderByDesc('from')
->first();
}
You might have guessed it already: it adds a query for each booking.
The best scenario would be to kind of eager load the "previous booking" (with) so that it is accessible like:
$booking->previous_booking->from
Is there any possible way to do it like this?
Constraints:
I can't query all bookings, order them by "from" and then just get the previous index
Take a look at this article by Jonathan Reinink. You can apply his solution by simulating a previous_booking_id on your Booking model. Then add the previousBooking() relationship and query that using with('previousBooking'). Let me know if you need any help on the implementation.
Update
Add the following scope to your Booking model:
public function previousBooking()
{
return $this->belongsTo(Booking::class);
}
public function scopeWithPreviousBooking($query)
{
$query->addSelect(['previous_booking_id' => Booking::select('id')
->whereColumn('previous_booking.from', '<', 'bookings.to')
->orderByDesc('from')
->take(1)
])->with('previousBooking');
}
You can now get all your bookings with their previous booking id included using this:
$bookings = Booking::withPreviousBooking()->get();
All bookings should now have a previous_booking_id. We add the with('previousBooking') to the end of the scope as if we query a relation of the booking. Eloquent does not care whether that previous_booking_id is in the database or not, as long as it's available in the model (which we've done by the addSelect() method).
You should now be able to use your requested solution in your view:
$booking->previousBooking->from
Note: you can only access the previousBooking relation if you've applied the scope. If not, the previous_booking_id is not available, and the relation will therefore be null. If you always want to load the previous booking, consider to add it as a global scope. However, I recommend to just apply the scope where it's needed.

Get back data from the pivot table when store a many-to-many relationship

I have a many-to-many relationship between projects and surveys. I can successfully create a relationship between a survey and a project with
$userSurvey = $project->surveys()->save($survey);.
This will create a new record inside the question_survey pivot table (The pivot table contains the columns id, question_id and survey_id.)
$userSurvey will receive the newly created survey model. Is there any way to receive also the id of the new record in the question_survey pivot table?
When retrieving many to many relationships, Laravel will automatically attach the pivot to the resulting Model, so in your case, $userSurvey will automatically have an attribute called pivot that holds, well, of course, the pivot.
But by default, that pivot attribute only holds the foreign keys, so in your case, the question_id and survey_id. If you have any other extra attributes,(in your case id), simply use the withPivot method, as follows.
public function surveys()
{
return $this->belongsToMany('App\Question', 'question_survey')
->withPivot('id');
}
Now you can access the id from the pivot table:
$userSurvey->pivot->id;
Bonus, if you think that the pivot word just does not fit your wording style, just use the as method in your relationship to customize the variable name of the pivot attribute.
public function surveys()
{
return $this->belongsToMany('App\Question', 'question_survey')
->as('question_survey')
->withPivot('id');
}
Now you can access the id from the pivot table:
$userSurvey->question_survey->id;

laravel 5 advance eloquent multiple selection

I have a table containing multiple artist. While creating an event (updating event table) I need to select artists that I want to perform at my event and need to add their ids in the event_artist (transitive) table (that contains event_id & artist_id). How shall I proceed?
You would define a many to many relationship.
In Artist model class:
public function events()
{
$this->belongsToMany(Event::class);
}
In Event model class:
public function artists()
{
$this->belongsToMany(Artist::class);
}
This assumes your pivot table is named artist_event (the other two table names combined in alphabetical order). Otherwise you can pass the pivot table name as the second argument to belongsToMany: belongsToMany(Event::class, 'event_artist').
Then you can attach an artist to an event using $event->artists()->attach($artist);. Or detach $event->artists()->detach($artist);. You can also sync a collection of artists: $event->artists()->sync($artists);. You can also attach, detach or sync events to artists. All three methods will accept either Eloquent models or ids.

Updating a pivot table in Eloquent

I've got a many to many relationship between a student and an institution_contact.
students should only ever have two institution_contacts and I have an attribute on the pivot table named type to be set as 1 or 2.
So, my pivot table looks like this:
institution_contact_student: id, institution_contact_id, student_id, type
I've run into difficulty in deciding how to approach the issue of adding/updating the pivot table. Let's say I have 100 students and I want to assign them a contact with the type of 1.
My current solution is to delete the contact then add it:
$students = Student::all(); // the 100 students
$contactId = InstitutionContact::first()->id; // the contact
foreach ($students as $student) {
// remove existing contact
$student
->institutionContacts()
->newPivotStatement()
->where('type', 1)
->delete();
// add new contact
$student
->institutionContacts()
->attach([$contactId => ['type' => 1]]);
}
However, I'm thinking that this is going to hit the database twice for each student, right? So would I be better off creating a model for the pivot table and removing all entries that matched the student id and the type then simply adding the new ones? Or would creating a model for the pivot table be considered bad practice and is there a better way of accomplishing this that I've missed?
Please note the reason I'm not using sync is because I'm relying on the type attribute to maintain only two contacts per student. I'm not aware of a way to modify an existing pivot without causing issues to my two contacts per student requirement.
Edit:
Instead of creating a model I could run the following code to perform the delete using DB.
DB::table('institution_contact_student') // the pivot table
->whereIn('student_id', $studentIds)
->where('type', 1)
->delete();
If I have understood your question correctly then you can use the updateExistingPivot method for updating your pivot table.But first of course you have to define the pivot in your relationship. For instance,
public function institutionContacts(){
return $this->belongsToMany('institutionContact')->withPivot('type');
}
after this, all you have to do is use the following code:
$student
->institutionContacts()
->updateExistingPivot($contactId, ["type" => 1]);
Hope this helps.

Resources