-
-
Notifications
You must be signed in to change notification settings - Fork 135
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
Self-Containing Classes #42
Comments
I did some testing and the following works for building the model: // ./src/internal/schema.ts
export function _buildSchema<T, U extends AnyParamConstructor<T>>(
cl: U,
sch?: mongoose.Schema,
opt?: mongoose.SchemaOptions
) {
// ...
if (!isNullOrUndefined(decorators)) {
for (const decorator of decorators.values()) {
_buildPropMetadata(decorator, cl); // pass down class
}
}
// ...
} // ./src/prop.ts
export function _buildPropMetadata<T, U extends AnyParamConstructor<T>>(input: DecoratedPropertyMetadata, cl: U) {
// ...
if (utils.isNotDefined(Type)) {
buildSchema(Type);
}
if (cl === Type) {
return;
}
// ...
} Now the model looks like // selfContaining.ts
import { prop } from '../../src/prop';
import { getModelForClass } from '../../src/typegoose';
class SelfContaining {
@prop()
public bla?: number;
@prop()
public nest?: SelfContaining;
}
const SelfContainingModel = getModelForClass(SelfContaining);
export default SelfContainingModel; And the test looks as following // selfContaining.test.ts
import { expect } from 'chai';
import SelfContainingModel, { SelfContaining } from '../models/selfContaining';
// Please try to keep this file in sync with ./arrayValidator.test.ts
/**
* Function to pass into describe
*/
export function suite() {
it('should not throw exception', (done) => {
expect(SelfContainingModel.create({
bla: 5,
nest: new SelfContaining()
})).to.eventually.be.fulfilled.and.notify(done);
});
} |
@lab9 could you make an PR? |
Will do so later. Info: it('should not throw exception', async () => {
const model: SelfContaining = await SelfContainingModel.create({
bla: 1,
nest: {
bla: 2,
nest: null
}
}) as SelfContaining;
expect(model.bla).to.be.equal(1);
expect(model.nest!!.bla).to.be.equal(2); // TypeError: Cannot read property 'bla' of undefined
}); |
thats is exactly what i couldnt figure out, because javascript isnt "recursive" in the meaning of "apply current object 'infinitly' down on this property"
because of that, it a pr is not needed anymore thanks for trying :) |
Have a look at the following more or less workaround: import { prop } from '../../src/prop';
import { getModelForClass } from '../../src/typegoose';
interface Folder {
displayName: string;
subFolders: Array<Folder>;
}
export class Storage {
@prop()
public bla?: number;
@prop()
public root: Folder;
}
const StorageModel = getModelForClass(Storage);
export default StorageModel; The test goes through: import { expect } from 'chai';
import StorageModel, { Storage } from '../models/storage';
// Please try to keep this file in sync with ./arrayValidator.test.ts
/**
* Function to pass into describe
* ->Important: you need to always bind this
*/
export function suite() {
it('should not throw exception', async () => {
const storageInstance: Storage = {
bla: 1,
root: {
displayName: 'root',
subFolders: [
{
displayName: 'sub-1',
subFolders: []
}
]
}
};
const mongoInstance: Storage = await StorageModel.create(storageInstance) as Storage;
expect(mongoInstance.bla).to.be.equal(storageInstance.bla);
expect(mongoInstance.root.displayName).to.be.equal(storageInstance.root.displayName);
expect(mongoInstance.root.subFolders.length).to.be.equal(storageInstance.root.subFolders.length);
expect(mongoInstance.root.subFolders[0].displayName).to.be.equal(storageInstance.root.subFolders[0].displayName);
});
} |
this is not a "valid" workaround, because an interface will get compiled to
|
Well yes you are right and thanks for pointing the |
sorry, no this wouldnt be a temporary solution, because it wouldnt be "temporary" because JS will never support "infinite" self-nesting and would make the code less readable (and only have limited nesting like 1-5 levels or something like that) Sorry that i didnt answer earlier, i just didnt really know what to say and needed to think about it |
Is there any progress in solving this issue? It is a standard use case to model a parent child relationship between entities of equal type... |
@mariusheine if it is as sub-doc, then no, there is not an fix / solution, but if it is just references, it works, there you need to use the name of the referenced model |
ahh yes. i was defining it as a ref but only inside the decorator. I did not set the type of property itself to Ref<...>. thx a lot :D |
@hasezoey what is the use case of a self-containing class, is it more about providing info to the mongoose schema of what properties an object will have, when not using a ref? |
@sebastiangug sorry, i dont understand what you are trying to say, i dont know an use-case where an class should contain itself as an subdocument (i mean no ref) |
@hasezoey ah sorry, i just understood what it actually means, I was confusing it with something else -- my bad! |
Here's something related: Mongoose has Not sure if it can be handled automatically though. |
@andreialecu sounds interesting, will try if i can find an good way to implement this (but i guess this will need an full schema analysis after the schemas are complete (maybe with an flag that marks that an property on the schema is recursive?)) |
Indeed, some sort of schema analysis is probably needed to handle all cases. But I believe @lab9 was onto something by fixing the Probably the starting point of implementing this is around here: // ./src/prop.ts
export function _buildPropMetadata<T, U extends AnyParamConstructor<T>>(input: DecoratedPropertyMetadata, cl: U) {
// ...
if (utils.isNotDefined(Type)) {
buildSchema(Type);
}
if (cl === Type) {
// schema.add()?
return;
}
// ...
} |
@andreialecu the problem is in |
Hi! I have this field in @Field((type) => [Category], { nullable: true })
@Property({
type: () => Category,
default: [],
required: false,
})
children?: Category[]; And I got this error:
Is there a way to add the class Category to it's field? |
@MihaiWill sorry, but your example dosnt include you class, so nothing can be said about the error yet (based from what is given, there shouldnt be an error) |
@ObjectType()
export default class Category {
@Field((type) => String)
_id: string;
@Field()
@Property()
title: string;
@Field((type) => [Category], { nullable: true })
@Property({
type: () => Category,
default: [],
required: false,
})
children?: Category[];
} |
@MihaiWill you are trying to make an self-containing class as sub-documents, which isnt possible at the moment |
@hasezoey got it, solved with ref, thanks for answer! |
I found another "solution" to keep @ObjectType()
export class NestedDto {
@prop()
@Field()
public code: string;
@prop({ type: NestedSubDto, default: [] })
@Field(() => [NestedDto])
public children: NestedDto[];
@prop()
public order: number;
}
export class NestedSubDto {
@prop()
public code: string;
@prop({ type: NestedDto, default: [] })
public children: NestedDto[];
@prop()
public order: number;
} Works pretty well for now. |
closing this, because it will very likely never be able to get fixed Note: self-references still work, but not self-nesting (subdocuments) |
Continuation of szokodiakos/typegoose#19
Versions
Code Example
Do you know why it happenes?
Typegoose tries to build the schemas that are a prop
PS: i have absolutely no clue on how to fix this
Edit 1: addition, i didnt just meant like the code example, it was just very basic, here an proper example:
The text was updated successfully, but these errors were encountered: