#HasOne from HasMany relation

35 messages · Page 1 of 1 (latest)

blissful aspen
#

Dear everyone,
I hope to make this clear and to get some help to resolve my issue.

I have 3 tables.
A, B e C. We can see C as a pivot table of A and B (A has many C records, B has many C records but exists, if any, one and only one record for a given A.id and B.id)
Now, how can I get, in a relation where I m querying A records, the related C records that belong to B?
So basically going from an HasMany, Relationship (Record of A hasMany records of C) to an HasOne relationship (from all the records of C belonging to each single record of A, I want only the ones where b_id = B.id)?

I m trying to get all the features of an HasOne relationship (especially eager loading) without the need to map all the A records to filter out the ones not wanted.

I was thinking of a function
// Model/A
return $this->Cs->where ('B_id',B->id)->first()

Or setting the attribute like
Attribute::make(set:FN(C $c)=>$this->Cs->where ('B_id',B->id)->first())

Both of the solutions don't work because 1. Eager loading not possible
2. Need to map all the results AFTER querying the A models

Is there a solution for this?
Ideally it s really simple, but it seems that no parameters can be passed to the HasMany To get an hasOne relationship

Ps: latest or oldest are useless, as the records related don't depend on dates .

winter oyster
hidden pine
#

How about writing a local scope on C, something like

public function scopeOfB(Builder $query, int $b_id) {
  $query->where('id', $b_id);
}

Then you could

$a = A::first();
$b = B::first();
$c_s = $a->c()->ofB($b->id)->get();

I don't know, its very hard to work out what you want without a more concrete example..

blissful aspen
#

Hi guys thanks,
here is a more detailed example:

I have 3 tables: players (Player), auctions (Auction) and auction_player (AuctionPlayer).

As you can tell, Player and Auction are not related in any way but:
Auction HasMany AuctionPlayer
Player HasMany AuctionPlayer
but exists one and only one, if any, AuctionPlayer belonging to a specific Player and a specific Auction (the couple player_id - auction_id on auction_player is unique)

Using a scope won't give me the single result, but more a collection with one element (and it's not what I want)
So
$a = A::first();
$b = B::first();
$c_s = $a->c()->ofB($b->id)->get();
Won't work.

As for the former suggestion, I don't know actually how to implement it, as What I want, in the end, is getting Player::with('auctionPlayer')->get()
(where AuctionPlayer should be the only one with $auction->id

hidden pine
#

What about $c_s = $a->c()->ofB($b->id)->sole(); But this will error if there isn't a related model.

blissful aspen
#

But in this case I can't use eager loading, but also, the related model is not always present, so it's not ok for my situation

hidden pine
#

Have you tried using a HaveOneThrough relationship?

class Player {
    public function auction(): HasOneThrough
    {
        return $this->hasOneThrough(Auction::class, AuctionPlayer::class);
    }
}
blissful aspen
#

I don't want auction through auctionplayer, but AuctionPlayer from Auction..

blissful aspen
#

I got it working with Mapping the whole collection.
But I don't think a beter solution doesn't exists.

winter oyster
#

what about that?

blissful aspen
#

No, because the relation I want is a combination of Auction and Player, and find the intersection between the two Tables that relies on the Pivot Table, which is AuctionPlayer.

BelongsToThrough, or HasOneThrough, supposes that Player and Auction are somehow related, which are not.
AuctionPlayer is the child of Player and Auction, but Player and Auction are not related.

Player can have only one AuctionPlayer for each Auction, but can have multiple AuctionPlayer.

#

The issue is that you can't pass parameters to relations.
Otherwise it would be very simple!

// App\Models\Player
public function auctionPlayers():HasMany{
return $this->hasMany(AuctionPlayer::class);
}
public function auctionPlayer(Auction $auction):HasOne{
return $this->hasOne(AuctionPlayer::class)->where('auction_id',$auction->id);
}

Of course this doesn't work, because when you do
Player::with('auctionPlayer');
It expects a parameter, that isn't there

hidden pine
#

I dunno, but if I was having a lot of difficulty setting up a relationship in a laravel app, I would be looking at how I am trying to structure my models as the problem. Lean into the framework, and take your queues from it would be my advice.

alpine badge
blissful aspen
#

I am probably not explaining myself correctly.

The issue with the relationship is that it doesn't work as intended when a Single Model is wanted.

It s how to go from a HasMany to a HasOne by passing a parameter.
Because, afaik, the only way to go from HasMany or HasOne is by using latest of many, oldest of many, etc., but this can't be applied cause the Auction it s not always nor the last nor the oldest.

alpine badge
#

If you had a belongsToMany between Player and Auction, you could retrieve the AuctionPlayer values by doing $player->auctions()->where('id', $auction_id)->first()->pivot

blissful aspen
alpine badge
#

What is the value of eager loading here when you're only fetching one result?

#

But to answer your question, ->with('auctions', fn($q) => $q->where('auctions.id', $auction_id))

blissful aspen
#

Beacuse I'm querying Player::all(), and for each I wan't to eager load the AuctionPlayer of the Auction I'm looking, that can be both active, or closed, old, or new, so any value of auction_id

alpine badge
blissful aspen
#

No I can't do that, beacause a Player which doesn't have an AuctionPlayer field can still be present

alpine badge
#

No they can't, that's not how pivots work

blissful aspen
#

Then someone place a bid and the AuctionPlayer it's created

alpine badge
#

Oooh

#

Just Player::with('auctions', fn($q) => $q->where('auctions.id', $auction_id)) then

blissful aspen
#

It will be a belongsToThrough, as Player belongsTo auctions Through AuctionPlayer.
Ok let me try,
thanks a lot anyways!

alpine badge
#

No, a belongsToMany, using AuctionPlayer as a pivot

blissful aspen
#

But I don't need the auction, I need the AuctionPlayer.
If I do with('auctions') (supposing that
class Player extends Model{
public function auctions():BelongsToMany
{
return $this->belongsToMany(Auction::class);
}
}
I will get all the auctions where the Player appears. (If I query them, with ->where('auctions.id', $auction_id)), I will get a collection of One elment)

But I want, from the auctionID, only the AuctionPlayer
I can't eager load auctionPlayer with first()->pivot() in the relation

alpine badge
#

Now that I've had my coffee, you can probably just do the same thing with auctionplayer

#

->with('auctionPlayer', fn($q) => $q->where('auction_id', $auction_id))

blissful aspen
alpine badge
#

Is that really such a big deal though?