Laravel: With and whereHas to filter second relation hasOne

921 Views Asked by At

i'm trying to filter the table using "with" and "whereHas" for the relation and have it follow a second second relation.

Is it possible to do it with "with" or would it only be possible with "Joins"?

Ticket >> StatusHistory (Last record) >> StatusName = 'new'

ticket
    -id 
    -name

status_history
    - ticket_id
    - status_name_id
    - timestamps

status_names
    - id
    - name  (new, close, paused)
<?

class Ticket extends Model
{

    public function latestStatus()
        {
            return $this->hasOne(StatusHistory::class, 'ticket_id', 'id')->latest();
        }




class StatusHistory extends Model
{
    public function statusName()
    {
        return $this->hasOne(StatusName::class, 'id', 'status_name_id');
    }

This usually works well if there is only one Status history record, but if there are more, it returns values that should not be there.

example:  ticket_id 1 has in history first status new and them status paused 

With this sentence he returned the ticket to me even so he no longer has the last status in "new".
    Ticket::with('latestStatus')
            ->whereHas('latestStatus.statusName', function($q){
                $q->where('name', 'new');
            })

2

There are 2 best solutions below

1
On

To access the first relationship you just use:

$ticket = Ticket::find($id);
$ticket->latestStatus

By having a "hasOne" relationship established, this will return the related record, which from what I see also has a hasOne relationship, so you can do the following:

$ticket->latestStatus->statusName

In this way, you are accessing the second relationship and working it as usual.

However, this is not the only way, as Laravel also offers access to chained relationships through the "has-one-through" method, which according to the documentation is defined as:

"...this relationship indicates that the declaring model can be matched with one instance of another model by proceeding through a third model."

class Ticket extends Model{
    public function statusName()
    {
        return $this->hasOneThrough(StatusName::class, StatusHistory::class);
    }
}

Take into account that for this you must follow the conventions established by Laravel. I leave here the related links, I am sure they will be very helpful. Greetings.

Relationships: one-to-one

Relationships: has-one-through

1
On

According to the documentation (https://laravel.com/docs/8.x/eloquent-relationships#constraining-eager-loads) it is possible. It would look like this:

    Ticket::with(['latestStatus' => function($q){
          $q->where('name', 'new');
    }])->get();

So that the subquery is linked to the relation you are trying to load