Skip to content

Commit 89cfb3d

Browse files
authored
feat: add two fan out methods to association loader (#52)
1 parent 35db7e9 commit 89cfb3d

File tree

2 files changed

+141
-2
lines changed

2 files changed

+141
-2
lines changed

packages/entity/src/EntityAssociationLoader.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,52 @@ export default class EntityAssociationLoader<
5555
return await loader.loadByIDAsync((associatedEntityID as unknown) as TAssociatedID);
5656
}
5757

58+
/**
59+
* Load many entities associated with this entity, often referred to as entites belonging
60+
* to this entity. In a relational database, the field in the foreign entity is a
61+
* foreign key to the ID of this entity. Also commonly referred to as a has many relationship,
62+
* where this entity has many associated entities.
63+
* @param associatedEntityClass - class of the associated entities
64+
* @param associatedEntityFieldContainingThisID - field of associated entity which contains the ID of this entity
65+
* @param queryContext - query context in which to perform the load
66+
*/
67+
async loadManyAssociatedEntitiesAsync<
68+
TAssociatedFields,
69+
TAssociatedID,
70+
TAssociatedEntity extends ReadonlyEntity<TAssociatedFields, TAssociatedID, TViewerContext>,
71+
TAssociatedPrivacyPolicy extends EntityPrivacyPolicy<
72+
TAssociatedFields,
73+
TAssociatedID,
74+
TViewerContext,
75+
TAssociatedEntity
76+
>
77+
>(
78+
associatedEntityClass: IEntityClass<
79+
TAssociatedFields,
80+
TAssociatedID,
81+
TViewerContext,
82+
TAssociatedEntity,
83+
TAssociatedPrivacyPolicy
84+
>,
85+
associatedEntityFieldContainingThisID: keyof TAssociatedFields,
86+
queryContext: EntityQueryContext = this.entity
87+
.getViewerContext()
88+
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
89+
.getQueryContextProvider()
90+
.getRegularEntityQueryContext()
91+
): Promise<readonly Result<TAssociatedEntity>[]> {
92+
const thisID = this.entity.getID();
93+
const loader = this.entity
94+
.getViewerContext()
95+
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
96+
.getLoaderFactory()
97+
.forLoad(queryContext);
98+
return await loader.loadManyByFieldEqualingAsync(
99+
associatedEntityFieldContainingThisID,
100+
thisID as any
101+
);
102+
}
103+
58104
/**
59105
* Load an associated entity identified by a field value of this entity. In a relational database,
60106
* the field in this entity is a foreign key to a unique field of the associated entity.
@@ -101,6 +147,52 @@ export default class EntityAssociationLoader<
101147
);
102148
}
103149

150+
/**
151+
* Load many associated entities identified by a field value of this entity. In a relational database,
152+
* the field in this entity refers to a field of the associated entity.
153+
* @param fieldIdentifyingAssociatedEntity - field of this entity containing the value with which to look up associated entities
154+
* @param associatedEntityClass - class of the associated entities
155+
* @param associatedEntityLookupByField - field of associated entities with which to look up the associated entities
156+
* @param queryContext - query context in which to perform the load
157+
*/
158+
async loadManyAssociatedEntitiesByFieldEqualingAsync<
159+
TAssociatedFields,
160+
TAssociatedID,
161+
TAssociatedEntity extends ReadonlyEntity<TAssociatedFields, TAssociatedID, TViewerContext>,
162+
TAssociatedPrivacyPolicy extends EntityPrivacyPolicy<
163+
TAssociatedFields,
164+
TAssociatedID,
165+
TViewerContext,
166+
TAssociatedEntity
167+
>
168+
>(
169+
fieldIdentifyingAssociatedEntity: keyof TFields,
170+
associatedEntityClass: IEntityClass<
171+
TAssociatedFields,
172+
TAssociatedID,
173+
TViewerContext,
174+
TAssociatedEntity,
175+
TAssociatedPrivacyPolicy
176+
>,
177+
associatedEntityLookupByField: keyof TAssociatedFields,
178+
queryContext: EntityQueryContext = this.entity
179+
.getViewerContext()
180+
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
181+
.getQueryContextProvider()
182+
.getRegularEntityQueryContext()
183+
): Promise<readonly Result<TAssociatedEntity>[]> {
184+
const associatedFieldValue = this.entity.getField(fieldIdentifyingAssociatedEntity);
185+
const loader = this.entity
186+
.getViewerContext()
187+
.getViewerScopedEntityCompanionForClass(associatedEntityClass)
188+
.getLoaderFactory()
189+
.forLoad(queryContext);
190+
return await loader.loadManyByFieldEqualingAsync(
191+
associatedEntityLookupByField,
192+
associatedFieldValue as any
193+
);
194+
}
195+
104196
/**
105197
* Load an associated entity by folding a sequence of {@link EntityLoadThroughDirective}. At each
106198
* fold step, load an associated entity identified by a field value of the current fold value.

packages/entity/src/__tests__/EntityAssociationLoader-test.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { enforceAsyncResult } from '@expo/results';
22

33
import EntityAssociationLoader from '../EntityAssociationLoader';
4+
import { enforceResultsAsync } from '../entityUtils';
45
import TestEntity from '../testfixtures/TestEntity';
56
import TestEntity2 from '../testfixtures/TestEntity2';
67
import TestViewerContext from '../testfixtures/TestViewerContext';
@@ -26,8 +27,28 @@ describe(EntityAssociationLoader, () => {
2627
});
2728
});
2829

29-
describe('loadRelatedEntityByFieldEqualingAsync', () => {
30-
it('loads associated entities by field equaling', async () => {
30+
describe('loadManyAssociatedEntitiesAsync', () => {
31+
it('loads many associated entities referencing this entity', async () => {
32+
const companionProvider = createUnitTestEntityCompanionProvider();
33+
const viewerContext = new TestViewerContext(companionProvider);
34+
const testEntity = await enforceAsyncResult(TestEntity.creator(viewerContext).createAsync());
35+
const testOtherEntity1 = await enforceAsyncResult(
36+
TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync()
37+
);
38+
const testOtherEntity2 = await enforceAsyncResult(
39+
TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync()
40+
);
41+
const loaded = await enforceResultsAsync(
42+
testEntity.associationLoader().loadManyAssociatedEntitiesAsync(TestEntity, 'stringField')
43+
);
44+
expect(loaded).toHaveLength(2);
45+
expect(loaded.find((e) => e.getID() === testOtherEntity1.getID())).not.toBeUndefined();
46+
expect(loaded.find((e) => e.getID() === testOtherEntity2.getID())).not.toBeUndefined();
47+
});
48+
});
49+
50+
describe('loadAssociatedEntityByFieldEqualingAsync', () => {
51+
it('loads associated entity by field equaling', async () => {
3152
const companionProvider = createUnitTestEntityCompanionProvider();
3253
const viewerContext = new TestViewerContext(companionProvider);
3354
const testOtherEntity = await enforceAsyncResult(
@@ -57,6 +78,32 @@ describe(EntityAssociationLoader, () => {
5778
});
5879
});
5980

81+
describe('loadManyAssociatedEntitiesByFieldEqualingAsync', () => {
82+
it('loads many associated entities by field equaling', async () => {
83+
const companionProvider = createUnitTestEntityCompanionProvider();
84+
const viewerContext = new TestViewerContext(companionProvider);
85+
const testEntity = await enforceAsyncResult(TestEntity.creator(viewerContext).createAsync());
86+
const testOtherEntity1 = await enforceAsyncResult(
87+
TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync()
88+
);
89+
const testOtherEntity2 = await enforceAsyncResult(
90+
TestEntity.creator(viewerContext).setField('stringField', testEntity.getID()).createAsync()
91+
);
92+
const loaded = await enforceResultsAsync(
93+
testEntity
94+
.associationLoader()
95+
.loadManyAssociatedEntitiesByFieldEqualingAsync(
96+
'customIdField',
97+
TestEntity,
98+
'stringField'
99+
)
100+
);
101+
expect(loaded).toHaveLength(2);
102+
expect(loaded.find((e) => e.getID() === testOtherEntity1.getID())).not.toBeUndefined();
103+
expect(loaded.find((e) => e.getID() === testOtherEntity2.getID())).not.toBeUndefined();
104+
});
105+
});
106+
60107
describe('loadAssociatedEntityThroughAsync', () => {
61108
it('chain loads associated entities', async () => {
62109
const companionProvider = createUnitTestEntityCompanionProvider();

0 commit comments

Comments
 (0)