-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
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
Integration for ES6 classes #4668
Conversation
Just looking ahead even further, eventually the Schema constructor could be an
which IMO looks very nice. |
} | ||
|
||
// Add static methods | ||
if (!virtualsOnly) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm what's the point of this virtualsOnly option? Looks like it's always true anyway...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suppose it's probably unnecessary. Its only use is mainly internal by Model.compile
. That's also why I didn't add JSDoc for the parameter.
It seems like Schemas need the virtuals defined on them directly for them to function properly, e.g. in toObject
and toJSON
. It just felt wasteful copying all of the statics and instance methods from the class onto the schema when applyMethods(model, schema)
and applyStatics(model, schema)
are being called immediately afterward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To clarify, loadClass
is intended to be public, used generally when someone might want to use a class to define virtuals, etc. on a subschema, like
const subSchema = new Schema({ ... });
subSchema.loadClass(class { ... });
const schema = new Schema({ field1: subSchema });
class MyModel extends Model { ... }
In this case virtualsOnly is undefined
.
if (typeof name === 'function') { | ||
model = name; | ||
name = model.name; | ||
if (!(model.prototype instanceof Model)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct me if I'm wrong.. I don't think this would work in the case when using a Base class on an individual class.
export class Foo extends MyBase {...}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, instanceof
looks through the whole prototype chain. So as long as MyBase
extends Model
, it's fine. I'm actually currently using this use of extends
for some of the models in my app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for clarifying that. I didn't know that about instanceof
; obviously :)
@@ -393,7 +401,7 @@ Mongoose.prototype.model = function(name, schema, collection, skipInit) { | |||
} | |||
|
|||
var connection = options.connection || this.connection; | |||
model = this.Model.compile(name, schema, collection, connection, this); | |||
model = this.Model.compile(model || name, schema, collection, connection, this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this change needed? name
is being set above when model is set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure where the problem you see is, can you elaborate? model
is unset if the name
passed in is a string, which means that Model.compile
receives a string name rather than an ES6 class, and everything proceeds normally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I just saw the diff and thought it wasn't necessary based on my misunderstanding of what the compile
method was doing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I first looked at this, I thought this bit was still inside the if (typeof name === 'function')..
scope.
I'm very interested in having this feature. I would like to decouple my application's classes from my data access layer. If I can provide an es6 class in lieu of the Mongoose Schema definition, then I can effectively use the same classes with a different database technology. Without this PR's implementation, is there a recommend way of achieving this? |
@mleanos Yeah, that is the direction I'd like to see mongoose go. This is mostly attainable using the |
@mleanos this is now merged and released in 4.7 |
@rockmacaca 👏 👏 👏 Thank you for this! |
🎉 |
🎉 |
Is there docs for this feature? |
@klimashkin I suppose there probably should be an example like the one I initially gave in the API docs. This shouldn't affect anything with However, depending on how you're set up, you might be able to natively add pre and post hooks like this: class MyClass extends Model {
async save() {
doSomethingPreSave();
const newDoc = await super.save();
doSomethingPostSave();
return newDoc;
}
} |
Awesome 🎉 |
This is great, I've been looking how to do this for ages! However I can't get it to work with discriminators/schema extension. Here's what I have (roughly): mongoose.model(
class LoginBase extends Model {},
new Schema({ enabled: Boolean }, { collection: 'logins', discriminatorKey: '_type' })
);
LoginBase.discriminator(
class LocalLogin extends LoginBase {},
new Schema({ email: String, password: String })
); This throws |
@alexis-y can you please open up a separate issue? Hard to track issues when new issues are nested underneath old ones :) |
Is there a doc to see how it should work? |
I assume this will work with typescript classes, but I don't see any mention of it. Will this work? Also as a side question, can you define the constructor in the class to extend the base Model constructor? Again it would seem so. |
When I use the syntax from the original post, i.e:
I get the following error: MissingSchemaError: Schema hasn't been registered for model "user.session". However, if I revert to:
I no longer get this error. |
var model; | ||
if (typeof name === 'function' && name.prototype instanceof Model) { | ||
model = name; | ||
name = model.name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is the '.name' defined?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because type of name=== 'function'
and model = name
model is a function. This function.prototype.name is what it's calling. So it would be "myFunction" in the below function function my function() {}
With a class this is the name of the class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if we want our model to have a name different than the class name, i.e if our model is name is 'my.function'?
We have to override it? i.e
class myFunction {
static get name(){
return 'my.function';
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name is a read-only property, so you can't change it
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name
I suggest you look at the link that vkarpov15 commented to use .LoadClass into the schema you want
@everythingspirals problem lies in /lib/index.js:390. Specifically:
Removing this allow it to work as expected. Not 100% positive on submitting a pull request as I'm sure there's a deeper issue but I myself do not have time to look into it. Perhaps a contributor like @vkarpov15 could look into this? |
@benjamin-halter can you provide a script that demonstrates the issue you're seeing? That'll make it much easier for me to look into this. |
In regards to this in case anyone else runs into this. If you are using a model class to define document methods, use the
|
The reason for this PR is that I noticed that mongoose under the hood is essentially acting as a mixin to a class, and I saw room for improvement with the syntax for defining models. Generally static and instance methods have to be inconveniently defined through helpers on a
Schema
before they are copied onto the model, andModel.compile
is also being inefficient by manually setting the prototype. So essentially, mongoose is doing work that ES6 already natively supports.With these changes
mongoose.model
can support taking an ES6 class wherename
is normally provided:I've also included a
loadClass
instance method for schemas that accomplishes some of whatmongoose-class-wrapper
can do. It allows similar functionality as the above, but it's useful for sub-schema definitions.This should be fully backward compatible and shouldn't introduce any breaking changes.