-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(repository): hasOne relation #2005
Conversation
Looks good to me at high level 👍 But! One of the has-many tests is failing, so your implementation may need few tweaks? I think the next task is to verify this implementation against real databases, e.g. MongoDB and MySQL. It would be great to have a full example app with hasOne relation configured for that. Can we come up with a meaningful entity that we could add to the existing todo-list example? |
2b6221b
to
fd03a78
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh, looks like I forgot to post few review comments the last time I was reviewing these changes.
packages/repository/test/acceptance/has-one.relation.acceptance.ts
Outdated
Show resolved
Hide resolved
ca49f91
to
3c96fea
Compare
3c96fea
to
93384d9
Compare
options, | ||
); | ||
if (found.length < 1) { | ||
// We don't have a direct access to the foreign key value here :( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we leave a TODO comment here? Maybe open a new issue to follow-up later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened #2114 as a follow-up issue.
packages/repository/test/unit/decorator/model-and-relation.decorator.unit.ts
Show resolved
Hide resolved
packages/repository/test/fixtures/repositories/customer.repository.ts
Outdated
Show resolved
Hide resolved
examples/todo-list/src/controllers/todo-list-image.controller.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks reasonably good for the first iteration.
Please address the comment containing exclamation mark ❗️ before landing this pull request.
Remaining comments can be addressed either as part of this PR or in a follow-up pull request.
packages/repository/src/relations/has-one/has-one-repository.factory.ts
Outdated
Show resolved
Hide resolved
packages/repository/src/relations/has-one/has-one-repository.factory.ts
Outdated
Show resolved
Hide resolved
packages/repository/src/relations/has-one/has-one-repository.factory.ts
Outdated
Show resolved
Hide resolved
packages/repository/src/relations/has-one/has-one.repository.ts
Outdated
Show resolved
Hide resolved
packages/repository/test/acceptance/has-one.relation.acceptance.ts
Outdated
Show resolved
Hide resolved
packages/repository/test/unit/decorator/model-and-relation.decorator.unit.ts
Show resolved
Hide resolved
packages/repository/test/unit/repositories/has-one-repository-factory.unit.ts
Outdated
Show resolved
Hide resolved
packages/repository/test/unit/repositories/has-one-repository-factory.unit.ts
Outdated
Show resolved
Hide resolved
3e3a9ac
to
9dea156
Compare
@@ -11,8 +11,11 @@ import {BelongsToDefinition, RelationType} from '../relation.types'; | |||
|
|||
/** | |||
* Decorator for belongsTo | |||
* @param targetResolver | |||
* @param definition | |||
* @param targetResolver A resolver function that retruns the target model for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: retruns
@@ -132,7 +132,7 @@ describe('createHasOneRepositoryFactory', () => { | |||
province: String; | |||
} | |||
|
|||
class Order extends Entity { | |||
class ModelWithoutIndexFK extends Entity { | |||
static definition = new ModelDefinition('Order') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please fix the model name string to match the class name.
@@ -142,18 +142,23 @@ describe('createHasOneRepositoryFactory', () => { | |||
type: 'string', | |||
required: true, | |||
}) | |||
.addProperty('id', { | |||
type: 'number', | |||
id: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am little bit confused. The model name is ModelWithoutIndexFK
, but it has a regular id
property. What am I missing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. I added an id
property because the model itself extends from Entity
, but the purpose here is the decorated relational property is not part of the composite index, thus the name ModelWithoutIndexFK
. Thoughts on improving it if it's confusing? I can extend from Model
and remove the id
property as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can extend from Model and remove the id property as well.
If this works, then I think it would be a good solution.
If not, then please capture the information from your comment above in a code comment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@b-admike Great effort! I haven't got enough time to review each single detail but the new example and test cases LGTM in general 👍
I have a question regarding the BelongsToUnique
decorator: My understanding is, regardless it's a hasMany relation or hasOne relation, one child instance could only have one parent, why would we create a specific belongsToUnique
decorator for hasOne?
a971555
to
e128d86
Compare
Great question @jannyHou. The reason we created a sugar syntax decorator |
e128d86
to
b9c51b3
Compare
b9c51b3
to
e57d9c1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please address my comments.
@property() | ||
registeredOn: Date; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not so obvious that RegistrationDate
deserves a hasOne
relation from the Customer
. Something like A customer has a public profile
is probably better.
const propertyMetaDefaults: Partial<PropertyDefinition> = { | ||
id: true, | ||
generated: false, | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intention to make the FK unique? If so, I think it's wrong to set id
to true
as it will add the FK to the PK. For example, (id, customerId)
becomes the PK.
I think we need to mark it as unique
. IIRC, we can mark it uniquely indexed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the intention to make the FK unique?
Yes.
If so, I think it's wrong to set id to true as it will add the FK to the PK. For example, (id, customerId) becomes the PK.
The way we have envisioned the current hasOne implementation, there is only one PK property, and that's customerId
, there is no property called id
. I.e. the primary key is a foreign key too.
I think we need to mark it as unique. IIRC, we can mark it uniquely indexed.
I am not sure if all databases support unique
constraint. I am also pretty sure our auto-migration implementation does not correctly define unique
constraint either.
As a result, by using unique
instead of id
, the default implementation of hasOne
in LB4 would not work correctly out of the box.
On the other hand, all databases are enforcing the uniqueness constraint on primary keys (id property), otherwise primary keys could not be used as unique identifiers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's too restrictive to promote the FK for hasOne
to be part of PK. At least we should check if there is an existing PK.
BTW, marking a property id: true
does not guarantee it will be enforced by the database. I think we should generate an index with {unique: true}
option per https://loopback.io/doc/en/lb3/Model-definition-JSON-file.html#indexes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not only we are promoting FK to be the only PK, but we also require the target od HasOne to have the same PK as the model it relates to. E.g. when a Customer has one PublicProfile, then both the customer instance and the profile instance share the same primary key value.
This is the only way I am aware of that offers strong guarantees about consistency and avoid possible race conditions in all databases we support in LoopBack.
I am more than happy to consider different approaches, as long as they work similarly well across the databases we have connectors for.
Should we open a new issue to continue this discussion?
* @param definition Optional metadata for setting up a belongsTo relation | ||
* @returns {(target: Object, key:string)} | ||
*/ | ||
export function belongsToUniquely<T extends Entity>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name is not easy to understand. The purpose is to say the FK should be unique.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe it's a warning sign that we should not overload @belongTo
for responsibility of @property
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that ideally, relation decorators like @belongsTo
and @hasMany
should not call @property
decorator under the hood. To get there, we would have to change the current design in a non-trivial way and break backwards compatibility along the way.
Do we want to delay hasOne
implementation?
Personally, I'd prefer to finish hasOne
implementation using the current approach, and then look into ways how to change the overall design.
The name is not easy to understand. The purpose is to say the FK should be unique.
Can you propose a better name that's easier to understand?
Our intention here is to provide a syntactic sugar that will allow users to tell what they want to accomplish (setup the other side of hasOne relation) without going into low-level details (setting up a PK property as a way to get uniqueness constraint + make queries fast).
Hello @b-admike , |
hey @RaphaelDrai, sorry just saw your comment; based on the comments from @raymondfeng and @bajtos and discussion with @raymondfeng, I've decided to try the |
Hello @b-admike, |
@RaphaelDrai Right now I'm using #2126 for enforcement of uniqueness on the target model instead of marking the foreign key as a primary key in the current design. @bajtos @raymondfeng and I had a discussion and since the memory connector and cloudant connector do not support unique indexes, we've decided to have the first release of |
Thanks you for your detailed and helpful answer. I read the previous messages about "belongsToUnique" decorator. I think that it could be helpful to have the option to use "belongsTo" or "belongsToUnique" in 'hasOne' relation. |
Thanks for your feedback @RaphaelDrai. Those sound like good use cases. Unfortunately, I'm going to revert |
**EDIT - Continuation of #1879 **
See discussion in #1956. @bajtos proposed using the PK of a target model in a
hasOne
relation to enforce uniqueness and 1-to-1 relation between source and target model instances (see #1956 (comment)).This is just a quick spike to see the feasibility of the proposal and get more feedback.Checklist
npm test
passes on your machinepackages/cli
were updatedexamples/*
were updated