1

Laravel 5.7. I have 2 Eloquent models: Owner, Cat.

Owner model:

public function cats()
{
    return $this->belongsToMany('App\Cat')->withPivot('borrowed');
}

Cat model:

public function owners()
{
    return $this->belongsToMany('App\Owner')->withPivot('borrowed');
}

The cat_owner pivot table has these fields:

id | cat_id | owner_id | borrowed
---------------------------------
1  |   3    |   2      |   1

I want my API to return a list of all cats, and if the logged-in user has borrowed this cat, I want the borrowed field to be set to true. This is what I have so far:

Controller:

public function index()
{
    return CatResource::collection(Cat::all());
}

CatResource:

public function toArray()
{
    $data = ['id' => $this->id, 'borrowed' => false];
    $owner = auth()->user();
    $ownerCat = $owner->cats()->where('cat_id', $this->id)->first();
    if ($ownerCat) {
        $data['borrowed'] = $ownerCat->pivot->borrowed == 1 ? true : false;
    }
    return $data;
}

This works, but it seems wasteful to request the $owner for every cat, e.g. if there's 5000 cats in the database. Is there a more efficient way to do this? I can think of 2 possible ways:

  1. Pass the $owner to the CatResource (requires overriding existing collection resource).

  2. Get this information in the controller first, before passing to the CatResource.

I prefer the second way, but can't see how to do it.

GluePear
  • 7,244
  • 20
  • 67
  • 120

2 Answers2

2

Try Conditional Relationship.

public function toArray($request)
{
    return [
      'id' => $this->id,
      'borrowed' => false,
      'borrowed' => $this->whenPivotLoaded('cat_owner', function () {
         return $this->owner_id === auth()->id() && $this->pivot->borrowed == 1 ? true : false;
      })
   ];
}

then call return CatResource::collection(Cat::with('owners')->get());

Cloud Soh Jun Fu
  • 1,456
  • 9
  • 12
  • There is no logic there to compare the cat owner with the logged-in owner. – GluePear Apr 04 '19 at 07:12
  • Thanks for persisting. Unfortunately the `borrowed` key is not being delivered. I guess `whenPivotLoading` is returning `false`. I am using `with('owners')`, so I'm not sure what's going wrong here. – GluePear Apr 04 '19 at 08:27
  • @GluePear did you solve this problem? I am struggeling with the exact same thing. – St. Jan Feb 10 '23 at 10:19
0

You are right, this does a lot of extra loading. I think you are running into the limitation that you can't know which record form cat_owner you want until you know both the records you're using from the cat and owner table.

For anyone still interested, my solution would be to make a resource that gives you just the pivot values

Since the following returns a collection you canNOT go to the pivot table on it:

/*
* returns a collection
*/
$data['borrowed'] = $this->owners

/*
 * So this doesNOT work. Since you can’t get the pivot
 * data on a collection, only on a single record
 */
$data['borrowed'] = $this->owners->pivot

You should receive the collection and then you can read the pivot data in the Resource of the owner Record. If this resource is only for the pivot data I would call it something like attributes.

create a new resourse for the attributes, something like:

class CatOwnerAttributeResource extends JsonResource
{
    public function toArray($request)
    {
      return [
          'borrowed' => $this->pivot->borrowed,
        ];
    }
}

Then receive the collection like so:

$data = ['id' => $this->id, 'borrowed' => false];

/*
 * Get the collection of attributes and grab the first (and only) record. 
 * NOTE: the filtering is done in the collection, not in the DBM. If there
 * is a possibility that the collection of owners who own this cat gets really 
 * big this is not the way to go! 
 */
if ($attributes =
  CatOwnerAttributeResource::collection(
    $this->owner
          ->where(‘id’ = $auth->user()->id)
          ->first() 
) {

  $data[‘borrowed’] = $attributes->borrowed == 1 ? true : false;
}
return $data;

Couldn’t run this code so please point errors out if you try it and it gives you any, I will adjust.

St. Jan
  • 284
  • 3
  • 17