From 7df0f63ce67659951b3bb4569f707161188103ba Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Sat, 16 Dec 2023 10:36:19 -0500 Subject: [PATCH] fix: Load relationships on search page load (#6295) --- app/components/DocumentBreadcrumb.tsx | 4 +++ app/models/base/Model.ts | 36 +++++++++++++++++++++++++-- app/models/decorators/Relation.ts | 3 +++ app/stores/CollectionsStore.ts | 31 +++-------------------- 4 files changed, 45 insertions(+), 29 deletions(-) diff --git a/app/components/DocumentBreadcrumb.tsx b/app/components/DocumentBreadcrumb.tsx index e087c38f8..f8a262dcc 100644 --- a/app/components/DocumentBreadcrumb.tsx +++ b/app/components/DocumentBreadcrumb.tsx @@ -68,6 +68,10 @@ const DocumentBreadcrumb: React.FC = ({ ? collections.get(document.collectionId) : undefined; + React.useEffect(() => { + void document.loadRelations(); + }, [document]); + let collectionNode: MenuInternalLink | undefined; if (collection) { diff --git a/app/models/base/Model.ts b/app/models/base/Model.ts index e1bd6d6f3..fcefdb6d3 100644 --- a/app/models/base/Model.ts +++ b/app/models/base/Model.ts @@ -3,6 +3,7 @@ import { set, observable, action } from "mobx"; import type Store from "~/stores/base/Store"; import Logger from "~/utils/Logger"; import { getFieldsForModel } from "../decorators/Field"; +import { getRelationsForModelClass } from "../decorators/Relation"; export default abstract class Model { static modelName: string; @@ -16,6 +17,7 @@ export default abstract class Model { @observable isNew: boolean; + @observable createdAt: string; @observable @@ -29,6 +31,36 @@ export default abstract class Model { this.isNew = !this.id; } + /** + * Ensures all the defined relations for the model are in memory + * + * @returns A promise that resolves when loading is complete. + */ + async loadRelations() { + const relations = getRelationsForModelClass( + this.constructor as typeof Model + ); + if (!relations) { + return; + } + + for (const properties of relations.values()) { + const store = this.store.rootStore.getStoreForModelName( + properties.relationClassResolver().modelName + ); + if ("fetch" in store) { + await store.fetch(this[properties.idKey]); + } + } + } + + /** + * Persists the model to the server API + * + * @param params Specific fields to save, if not provided the model will be serialized + * @param options Options to pass to the store + * @returns A promise that resolves with the updated model + */ save = async ( params?: Record, options?: Record @@ -93,7 +125,7 @@ export default abstract class Model { * Returns a plain object representation of fields on the model for * persistence to the server API * - * @returns {Record} + * @returns A plain object representation of the model */ toAPI = (): Record => { const fields = getFieldsForModel(this); @@ -104,7 +136,7 @@ export default abstract class Model { * Returns a plain object representation of all the properties on the model * overrides the native toJSON method to avoid attempting to serialize store * - * @returns {Record} + * @returns A plain object representation of the model */ toJSON() { const output: Partial = {}; diff --git a/app/models/decorators/Relation.ts b/app/models/decorators/Relation.ts index f61bbd5cf..84c64da9d 100644 --- a/app/models/decorators/Relation.ts +++ b/app/models/decorators/Relation.ts @@ -49,6 +49,9 @@ export const getInverseRelationsForModelClass = (targetClass: typeof Model) => { return inverseRelations; }; +export const getRelationsForModelClass = (targetClass: typeof Model) => + relations.get(targetClass.modelName); + /** * A decorator that records this key as a relation field on the model. * Properties decorated with @Relation will merge and read their data from diff --git a/app/stores/CollectionsStore.ts b/app/stores/CollectionsStore.ts index 009c55174..1585b4a33 100644 --- a/app/stores/CollectionsStore.ts +++ b/app/stores/CollectionsStore.ts @@ -11,7 +11,6 @@ import { } from "@shared/types"; import Collection from "~/models/Collection"; import { client } from "~/utils/ApiClient"; -import { AuthorizationError, NotFoundError } from "~/utils/errors"; import RootStore from "./RootStore"; import Store from "./base/Store"; @@ -164,32 +163,10 @@ export default class CollectionsStore extends Store { } @action - async fetch( - id: string, - options: Record = {} - ): Promise { - const item = this.get(id) || this.getByUrl(id); - if (item && !options.force) { - return item; - } - this.isFetching = true; - - try { - const res = await client.post(`/collections.info`, { - id, - }); - invariant(res?.data, "Collection not available"); - this.addPolicies(res.policies); - return this.add(res.data); - } catch (err) { - if (err instanceof AuthorizationError || err instanceof NotFoundError) { - this.remove(id); - } - - throw err; - } finally { - this.isFetching = false; - } + async fetch(id: string, options?: { force: boolean }): Promise { + const model = await super.fetch(id, options); + await model.fetchDocuments(options); + return model; } @computed