Why are key conventions for belongsTo relations inconsistent? #49569
Unanswered
kokoshneta
asked this question in
Q&A
Replies: 2 comments 1 reply
-
*bump* No one has any idea why this confusingly inconsistent convention was adopted? |
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
[Disclaimer: Trying to keep all these relations and their inter-relatedness in my head is melting my brain, so please feel free to let me know if I write some things backwards here and there. Also apologies in advance for the length.]
When defining relations on a model without explicitly providing the optional parameters for table and key names, Laravel uses a set of conventions to automatically generate various values needed for the database query to fetch related models: the table to fetch from (and the intermediary table, in the case of ‘through’ relations); the columns to search for (foreign keys in the local, intermediate or remote table, depending on relation type – filter keys as an umbrella term); and the model attributes that hold the values which will be parametrised into the query (value attributes). But the logic behind these conventions, particularly the last one, differs between relation types in a way that don’t make any sense to me.
I’ll list the conventions in tabular form below using the following terms for the different parts of a relationship:
caller
– model where the relationship is defined (or its snake-cased class name)called
– model being loaded in the relation (or its snake-cased class name)used
– intermediary model in ‘through’ relations (or its snake-cased class name)called
table namecaller
_[caller
PK]caller
PKcalled
table namecaller
_[caller
PK]caller
PKused
table namecaller
_[caller
PK]caller
PKcalled
table nameused
_[used
PK]used
PKused
table namecaller
_[caller
PK]caller
PKcalled
table nameused
_[caller
PK]used
PKcalled
table namecaller
PKcalled
PK]called
+caller
(alphabetical, joined with underscore)
called
PKcalled
_[called
PK]called
table namecaller
_[caller
PK]caller
PKAs should hopefully be clear from that table, all relation ‘steps’ that don’t require a pivot table use the table name defined on the
called
(orused
) model as the table to fetch from; that’s consistent. Pivot tables have their own logic – by necessity, since they don’t correspond to a model class. To compile the name of the filter column, all relation types use the snake-case name of the appropriate model (caller
,used
orcalled
, depending on relation type) and the column name of that model’s primary key, joined with an underscore (qualified by a pivot table name if relevant). Again, consistent.My issue is with the final column in the table. To choose which model attribute holds the value to filter by, all relation types use the primary key attribute on the logically appropriate model – except
belongsTo
relations, which use[method name]_id
.This is obscured in the documentation because all the examples are so simplified (all models have
id
as their primary key, table names that correspond to the class name, and foreign keys that always match their respective class names), but of course, in real-life legacy projects, such luxury is rarely to be expected.If the above is all a bit abstract, here’s an example comparing a
hasOne
and abelongsTo
to illustrate the inconsistency, with comments to show the query that would be executed and how the computed values are computed (in simplified pseudo-code):So the
hasOne
relation uses the related model name (snake-cased) + its primary key column as the column name to filter by, whereas thebelongsTo
relation uses the method name itself + the related model’s primary key column.Obviously, a model may have multiple
belongsTo
relations referencing the same related model but with different names – e.g., in a genealogical table aPerson
may have twobelongsTo
relations to otherPerson
models, one namedfather()
and one namedmother()
. In such cases, using the method name makes sense in abelongsTo
relation, since the model will not have aperson_id
attribute, but instead amother_id
andfather_id
.If the relation method name and the related class name are identical, the distinction is moot, but they frequently are not – even if there aren’t multiple relations to the same model. For example, any number of models may belong to some kind of ‘type’ (a
Shipment
with aShipmentType
, aPublication
with aPublicationType
, etc.), and the obvious name for a relation method for that would just betype()
, so you’d have$shipment->type
and$publication->type
. In such cases, compiling the column name from the method name won’t work, and you’ll have to provide the column name explicitly.It seems to me that the base assumption ought to be that a model will only have one relation per related model class, and that the database column will be based on the related model, not whatever you choose to name the method in your application. This would make the logic behind how column names are computed comparable across all relation types, instead of having one specific type that just does things completely differently.
Aside from ease when having multiple relations to the same model, is there a reason why
belongsTo
relations don’t use the related model’s class name like all other relation types?Beta Was this translation helpful? Give feedback.
All reactions