Search code examples
mongodbrelationshiploopbackjsloopback4

Loopback-next ReferenceMany Unable to find _id


I have user Model, which reference to ListedStocks,

@referencesMany(
    () => ListedStocks,
    {},
    {
      mongodb: {dataType: 'ObjectId'},
    },
  )
  stockIds?: string[];

But when i try to Create a user and pass an Array of Stocks _id, it gives me an error.

Error: Entity not found: ListedStocks with id "constraint {\"_id\":[\"62eeb4b42b59f883f02f381b\"]}"

When i tried To Debug this, I saw this Query.

loopback:connector:mongodb all ListedStocks { where: { _id: [ '62eeb4b42b59f883f02f381b' ] } } null []

shouldn't where : {_id: []} be where: {_id: {$in: []}}

Or am i missing something?


Solution

  • The Issue was in the constraint created by the createReferencesManyAccessor

    The constraint const constraint: any = {[primaryKey]: foreignKeyValue}; should have been constraint = {[primaryKey]: {inq:foreignKeyValue}};

    Complete Fix is Below.

    NOTE: This fix is specifically for the MongoDB, not sure if this breaks SQL Based Repos

    export class TempReferencesManyRepository<
      TargetEntity extends Entity,
      TargetIds,
      TargetRepository extends EntityCrudRepository<TargetEntity, TargetIds>,
      > implements ReferencesManyRepository<TargetEntity>
    {
      /**
       * Constructor of DefaultReferencesManyEntityCrudRepository
       * @param getTargetRepository - the getter of the related target model repository instance
       * @param constraint - the key value pair representing foreign key name to constrain
       * @param foreignKeyValue - PrimaryKey for Constraint
       * the target repository instance
       */
      constructor(
        public getTargetRepository: Getter<TargetRepository>,
        public constraint: DataObject<TargetEntity>,
        protected foreignKeyValue: any,
      ) {}
    
      async get(options?: Options): Promise<TargetEntity> {
        const targetRepo = await this.getTargetRepository();
        const result = await targetRepo.find(
          constrainFilter(undefined, this.constraint),
          options,
        );
        let expectedLength = 1;
        if(Array.isArray(this.foreignKeyValue)){
          expectedLength = this.foreignKeyValue.length;
        }
        if (result.length !== expectedLength) {
          // We don't have a direct access to the foreign key value here :(
          const id = 'constraint ' + JSON.stringify(this.constraint);
          throw new EntityNotFoundError(targetRepo.entityClass, id);
        }
        console.log("RESULT: ",result);
        return result[0];
      }
    }
    export function createReferencesManyAccessor<
      Target extends Entity,
      TargetIds,
      Source extends Entity,
      SourceId,
      >(
      referencesManyMetadata: ReferencesManyDefinition,
      targetRepoGetter: Getter<EntityCrudRepository<Target, TargetIds>>,
      sourceRepository: EntityCrudRepository<Source, SourceId>,
    ): ReferencesManyAccessor<Target, SourceId> {
      const meta = resolveReferencesManyMetadata(referencesManyMetadata);
      debug('Resolved ReferencesMany relation metadata: %o', meta);
      const result: ReferencesManyAccessor<Target, SourceId> =
        async function getTargetInstancesOfReferencesMany(sourceId: SourceId) {
          const foreignKey = meta.keyFrom;
          const primaryKey = meta.keyTo;
          const sourceModel = await sourceRepository.findById(sourceId);
          const foreignKeyValue = sourceModel[foreignKey as keyof Source];
          // workaround to check referential integrity.
          // should be removed once the memory connector ref integrity is done
          // GH issue: https://github.com/loopbackio/loopback-next/issues/2333
          if (!foreignKeyValue) {
            return undefined as unknown as Target;
          }
          //FIXME: This is MONGODB Specific Fix, MIGHT BROKE in SQL.
          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          let constraint: any = {[primaryKey]: foreignKeyValue};
          if(Array.isArray(foreignKeyValue)){
            constraint = {[primaryKey]: {inq: foreignKeyValue}};
          }
          const constrainedRepo = new TempReferencesManyRepository(
            targetRepoGetter,
            constraint as DataObject<Target>,
            foreignKeyValue
          );
          return constrainedRepo.get();
        };
    
      result.inclusionResolver = createReferencesManyInclusionResolver(
        meta,
        targetRepoGetter,
      );
      return result;
    }
    
    export default class ReferenceManyCurdRepository<
        T extends Entity,
        ID,
        Relations extends object = {}
      > extends DefaultCrudRepository<T, ID, Relations>{
      protected createReferencesManyAccessorFor<Target extends Entity, TargetId>(
        relationName: string,
        targetRepoGetter: Getter<EntityCrudRepository<Target, TargetId>>,
      ): ReferencesManyAccessor<Target, ID> {
        const meta = this.entityClass.definition.relations[relationName];
        return createReferencesManyAccessor<Target, TargetId, T, ID>(
          meta as ReferencesManyDefinition,
          targetRepoGetter,
          this,
        );
      }
    }