-
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
Model relation: hasManyThrough #2264
Comments
I attempted to reimplement hasManyThrough using the hasMany relation with a The hasManyThrough has to make a database query before creating a constraint. This forces the To keep backwards compatibility, I tried to make the HasManyRepository<Target> | Promise<HasManyRepository<Target>> |
Another issue I ran into is the generics. hasManyThrough needs access to the through repository. This requires passing the repository type using generics, very similar to the way the target repository type gets passed through the generic. export interface Through extends Entity {}
export function createHasManyRepositoryFactory<
Target extends Entity,
TargetID,
ForeignKeyType,
Through = Through,
ThroughID = string
>(
relationMetadata: HasManyDefinition,
targetRepositoryGetter: Getter<EntityCrudRepository<Target, TargetID>>,
throughRepositoryGetter?: Getter<EntityCrudRepository<Through, ThroughID>>,
) {} The challenge with this is that many times, hasMany is not using a through model and repository, so the through generic needs to be optional. This requires setting default generic values, and in my opinion is not ideal. Because the through generics are optional, this means they must be listed last. I would prefer the This is why I think hasManyThrough should be defined in a separate relation altogether. |
I would like to mention that using the hasManyThrough relation will be a little odd no matter how it is implemented because the constraint depends on asynchronous database queries. Using it will look something like the following. Notice the two awaits const patients = await (await this.physicianRepository.patients(physicianId)).find(); |
It would be great if ya'll could give me feedback on this. |
I was just thinking, maybe the constraint for hasManyThrough could be built in the hasManyThrough repository (for example in the actual |
Ok, so I actually was able to get https://github.com/codejamninja/loopback-next/blob/codejamninja/has-many-through-using-has-many/packages/repository/src/relations/has-many/has-many-repository.factory.ts#L55-L74 Because I can do it with an asynchronous getConstraint function, I think I will be able to stick with implementing this in the |
This also fixes the double await issue. |
I have a very basic example of You will need to use the code from the following remote and branch to get this to work. cd loopback-next/packages/repository
yarn build
yarn link cd medical-practice-api
yarn link @loopback/repository
yarn start this.physicians = this.createHasManyRepositoryFactoryFor(
'physicians',
getPhysicianRepository,
getAppointmentRepository
); @hasMany(() => Physician, { through: () => Appointment })
physicians: Physician[]; |
While I do believe the hasMany through relation should be built as part of the hasMany relation to maintain consistent behaviour with loopback 3, the repository and factory logic gets really messy and complex. IMO, the messiest part is dealing with the through repository factory. The factory function needs a generic type referencing the through entity. However, when not using a hasManyThrough relation, the generic type for the through entity should not be passed through. Origionally I solved this delima by simply not passing the through entity generic and instead directly referenced entity. This is certianly not ideal as it limits the type checking when using through with the hasMany factory. I decided that the best compromise is to still have hasMany contain the hasManyThrough realtion, but separated the relation repository factories. So, in summary if you want to use hasManyThrough, you just add the https://github.com/codejamninja/loopback-next/blob/codejamninja/has-many-through-using-has-many/packages/repository/src/relations/has-many/has-many-through-repository.factory.ts#L50 https://loopback.io/doc/en/lb3/HasManyThrough-relations.html#defining-a-hasmanythrough-relation |
If my previous comments are too technical, here are two simple examples of a hasMany w/o throughThis works exactly how hasMany has worked in the past with loopback 4 models/patient.model.ts import { Entity, model, property, hasMany } from '@loopback/repository';
import { Patient } from '../models';
@model()
export class Physician extends Entity {
@property({
type: 'string',
id: true
})
id?: string;
@hasMany(() => Patient)
patients: Patient[];
} repositories/patient.repository.ts import {
DefaultCrudRepository,
HasManyRepositoryFactory,
repository
} from '@loopback/repository';
import { inject, Getter } from '@loopback/core';
import { MemoryDataSource } from '../datasources';
import { Patient, Physician } from '../models';
import { PhysicianRepository } from '../repositories';
export class PatientRepository extends DefaultCrudRepository<
Patient,
typeof Patient.prototype.id
> {
public readonly physicians: HasManyRepositoryFactory<
Physician,
typeof Patient.prototype.id
>;
constructor(
@inject('datasources.memory')
dataSource: MemoryDataSource,
@repository.getter('PhysicianRepository')
getPhysicianRepository: Getter<PhysicianRepository>
) {
super(Patient, dataSource);
this.physicians = this.createHasManyRepositoryFactoryFor(
'physicians',
getPhysicianRepository
);
}
} hasMany w/ throughNotice the factory function is different models/patient.model.ts import { Entity, model, property, hasMany } from '@loopback/repository';
import { Appointment, Patient } from '../models';
@model()
export class Physician extends Entity {
@property({
type: 'string',
id: true
})
id?: string;
@hasMany(() => Patient, { through: () => Appointment })
patients: Patient[];
} repositories/patient.repository.ts import {
DefaultCrudRepository,
HasManyThroughRepositoryFactory,
repository
} from '@loopback/repository';
import { inject, Getter } from '@loopback/core';
import { MemoryDataSource } from '../datasources';
import { Patient, Physician } from '../models';
import { AppointmentRepository, PhysicianRepository } from '../repositories';
export class PatientRepository extends DefaultCrudRepository<
Patient,
typeof Patient.prototype.id
> {
public readonly physicians: HasManyThroughRepositoryFactory<
Physician,
typeof Patient.prototype.id
>;
constructor(
@inject('datasources.memory')
dataSource: MemoryDataSource,
@repository.getter('AppointmentRepository')
getAppointmentRepository: Getter<AppointmentRepository>,
@repository.getter('PhysicianRepository')
getPhysicianRepository: Getter<PhysicianRepository>
) {
super(Patient, dataSource);
this.physicians = this.createHasManyThroughRepositoryFactoryFor(
'physicians',
getPhysicianRepository,
getAppointmentRepository // notice the through repository getter
);
}
} |
@codejamninja, thanks for taking up this story. Is it ok if I assign this issue to you? I've already marked it as |
@dhmlau absolutely, thanks! |
Anyone wanting to get early access to the hasManyThrough relation (or anyone wanting to test it), you can install it the following way. npm install --save @loopback/repository@git+https://[email protected]/codejamninja/loopback-next.git#npm/has-many-through or add the following to your package.json file {
"dependencies": {
"@loopback/repository": "git+https://[email protected]/codejamninja/loopback-next.git#npm/has-many-through"
}
} |
@codejamninja Any updates on this feature? |
I will have updates before the end of the month. I'm a bit crunched with work at the moment. |
My pull request for this at #2359 is ready. For those interested in trying it out, you can install it the following way. npm install --save @loopback/repository@git+https://[email protected]/codejamninja/loopback-next.git#npm/codejamninja/[email protected] or add the following to your package.json file {
"dependencies": {
"@loopback/repository": "git+https://[email protected]/codejamninja/loopback-next.git#npm/codejamninja/[email protected]"
}
} |
Nevermind. Run |
What's the status of this feature branch? Any plans to get this merged? |
We are currently working on landing it via smaller PRs. A few of them have already landed - https://github.com/strongloop/loopback-next/pulls?q=is%3Apr+is%3Aclosed+hasManyThrough. |
Thanks for the interest in the cc @agnes512 |
Hi @dhmlau , Do you mean its on latest version now? can we use it like.. |
@MohammedAlasaad The code are released in |
@agnes512 Just updated the application dependencies and now the latest version is "@loopback/core": "^2.9.2" but still not able to create hasManyThrough relation like @hasmany(() => Patient, { through: () => Appointment }) |
@savan-thakkar Hi, could you check if you have the dependency |
@agnes512 Thanks for quick reply and yes its there "@loopback/repository": "^2.10.0" but still I am not able to define the relation. |
@savan-thakkar these are the dependencies:
|
@agnes512 Thanks for the update. I have checked all the dependencies and everything looks okay but still face issue saying 'Property model is missing in type' when I define the relation like "through: () => MyModelName". |
Closing as done. Feel free to leave comment or open issues if you have questions or find anything wrong. Thanks. |
@savan-thakkar any luck? |
@agnes512 Yes its fixed by defining the relation like @hasmany(() => TargetModel, {through: {model: () => ThroughModel}}). But apart from this I have one more doubt/issue related to saving additional fields of pivot table. |
@savan-thakkar
The |
@agnes512 Yes exactly, I am asking for the same. But is it possible to pass both the model's data in same create request and save the data ? However right now we are able to save the source and target ids automatically in pivot table by create api but not the additional fields of pivot table |
@savan-thakkar Yes, you should be able to do it if the API takes await userRepo
.users(existedUserId)
.create(
{name: 'target model - user'},
{throughData: {description: 'a through model - UserLink'}},
); |
@agnes512 Thank you for the exaplaination and help. It works 💯 Thanks again |
Open a separate issue to track
hasManyThrough
per request from @RaphaelDrai in#2043 (comment)
The text was updated successfully, but these errors were encountered: