Mongoose: use plugin in Schema static method - node.js

I use mongoose random plugin.
In my schema definition i call
GameSchema.plugin(random, { path: 'r' });
After that I have a custom static method who use the plugin:
GameSchema.statics.someMethod {
[...]
GameSchema.findRandom...
And I get the error
TypeError: Object #<Schema> has no method 'findRandom'
Is there a way to achieve what I am trying to do or should I implement some kind of repository ?
EDIT:
Ben's answer worked, I needed to use findRandom on the model and not the schema.
Precision for those in my case: you need to declare first your static function
GameSchema.statics.someMethod {
[...]
Game.findRandom...
then register your schema
var Game = mongoose.model('Game', GameSchema);
otherwise you'll get "Model .... has no method 'someMethod'"
Game variable in the static function is recognized event though it is defined only later in the script.
=> bonus question: does anyone know why it works ?

You're calling the method on the schema, whereas you need to be calling it on the model.
var Game = mongoose.model('Game', GameSchema);
Game.findRandom()...

Related

Attempting to use the mongoose "findOne" build-in method in a generic function

I have a typescript generic function as following:
function foo<T>(model:T): Promise<Response>{
return model.findOne(email)
}
to call the function I do the following:
const result = await foo<'IUserModel>(User)'>
(* for those who are confused about the ' ', please ignore them *)
when I do this, I receive the following error which is:
*** Property 'findOne does not exist' on type 'T' ***
I read the mongoose documentation about the Model object but this did not help me.
Which interface or type or alternative solution is available to get rid of this typescript error?
Thank you in advance!
Using a generic function without a constraint (extends) in the parameter tells TypeScript that model can be anything. So you could use a number, for example, and a number doesn't have the .findOne() function:
function foo<T>(model: T): Promise<Response> {
return model.findOne() // error
}
foo(10) // no error
Using extends tells TypeScript that the model must have the Model properties, so it will always have the findOne() function:
import { Model } from 'mongoose';
function foo<T extends Model>(model: T): Promise<Response> {
return model.findOne() // no error
}
foo(10) // error, it must be a Model
#Lucas Surdi Franco had opened a way of solution for me.
to resolve my problem I had to extend T with Model
code solution:
import { Model } from 'mongoose';
function foo<T extends Model<T>>(model: T): Promise<Response> {
return model.findOne()
If there is a nicer solution for this, feel free to comment

Typescript error TS2504 when trying to use async iterators with Mongoose 5.11.14: must have a '[Symbol.asyncIterator]()'

I'm trying to move away from using #types/mongoose after realising that mongoose 5.11 has type information.
However I'm now encountering Typescript issues when running tsc on the following code:
for await (const document of db.myModel.find()) {
...
}
The error is:
Type 'Query<IMyDocumentType[], IMyDocumentType>' must have a '[Symbol.asyncIterator]()' method that returns an async iterator.
Ignoring the error and running the code works fine.
Am I missing something or is there missing type information in mongoose 5.11.14 that #types/mongoose had?
mongoose 5.11.14
node.js 12.19.0
Yes, there is missing type information.
#types/mongoose#5.10.3 defines [Symbol.asyncIterator] on DocumentQuery, and Query extends DocumentQuery.
mongoose#5.11.14 does not define [Symbol.asyncIterator] on Query.
This method must be defined on any interface that is used on the right-hand side of a for await...of statement.
I do notice that both packages define a QueryCursor which extends stream.Readable. This class does define a [Symbol.asyncIterator] method, and the cursor() method of the Query interface returns an instance of QueryCursor. Try to see if the following compiles and runs as you expect without using #types/mongoose:
for await (const document of db.myModel.find().cursor()) {
// ^^^^^^^^^
...
}

Calling function with callback defined as string

var method = 'serviceName.MethodName'
I Just want to call it like
serviceName.methodName(function(output callback){
});
Is there any approach to call it.thanks
There are two methods that I can think of now.
JS eval
You can use the javascript eval function to convert any string into code snippet like below. Although eval is a quick solution but should not be used unless you dont have any other option by your side.
var method = 'UserService.getData';
eval(method)();
Factory pattern
Use a below pattern to get the service
You would need to define the services in such a manner that you can access them using a pattern.
var Services = {
// UserService and AccountsService are again objects having some callable functions.
UserService : {getData: function(){}, getAge: function(){}},
AccountsService : {getData: function(){}, getAge: function(){}},
// getService is the heart of the code which will get you the required service depending on the string paramter you pass.
getService : function(serviceName){
var service = '';
switch(serviceName){
case 'User':
service = this.UserService;
break;
case 'Accounts':
service = this.AccountsService;
break;
}
return service;
}
}
You can use get the required service with below code
Services.getService('User')
I'm not aware of any way you can resolve the serviceName part of that string to an object, without using eval. So obviously you need to be extremely careful.
Perhaps:
if (method.match(/^[a-zA-Z0-9_]+\.[a-zA-Z0-9_]+$/) {
var servicePart = eval(method.split('.')[0]);
var methodPart = method.split('.')[1];
servicePart[methodPart](...)
}
There are two separate problems in your question:
How to access object property by property name (string)?
How to access object by it's name (string)?
Regarding the first problem - it is easy to access object property by string using the following notation:
const myObject = {
myProp: 1,
};
console.log(myObject['myProp']);
And regarding the second problem - it depends on what serviceName is:
if it is a property of some other object, then use someObject['serviceName']['MethodName']
if it is a local variable, consider using a Map (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) to associate strings with objects;

How to add context helper to Dust.js base on server and client

I'm working with Dust.js and Node/Express. Dust.js documents the context helpers functions, where the helper is embedded in the model data as a function. I am adding such a function in my JSON data model at the server, but the JSON response to the browser doesn't have the function property (i.e. from the below model, prop1 and prop2 are returned but the helper property is not.
/* JSON data */
model: {
prop1: "somestring",
prop2: "someotherstring",
helper: function (chunk, context, bodies) {
/* I help, then return a chunk */
}
/* more JSON data */
I see that JSON.stringify (called from response.json()) is removing the function property. Not sure I can avoid using JSON.stringify so will need an alternative method of sharing this helper function between server/client. There probably is a way to add the helper functions to the dust base on both server and client. That's what I'm looking for. Since the Dust docs are sparse, this is not documented. Also, I can't find any code snippets that demonstrate this.
Thanks for any help.
send your helpers in a separate file - define them in a base context in dust like so:
base = dust.makeBase({foo:function(){ code goes here }})
then everytime you call your templates, do something like this:
dust.render("index", base.push({baz: "bar"}), function(err, out) {
console.log(out);
});
what this basically does is it merges your template's context into base, which is like the 'global' context. don't worry too much about mucking up base if you push too much - everytime you push, base recreates a new context with the context you supplied AND the global context - the helpers and whatever variables you defined when you called makeBase.
hope this helps
If you want stringify to preserve functions you can use the following code.
JSON.stringify(model, function (key, value) {
if (typeof(value) === 'function') {
return value.toString();
} else {
return value;
}
});
This probably doesn't do what you want though. You most likely need to redefine the function on the client or use a technology like nowjs.

Inheritance in MongoDb: how to request instances of defined type

This is how I used to utilize inheritance in Entity Framework (POCO):
ctx.Animals // base class instances (all instances)
ctx.Animals.OfType<Cat> // inherited class Cat's instances only
ctx.Animals.OfType<Dog> // inherited class Dog's instances only
This is the only similar way I found in MongoDb (MongoDb reference):
var query = Query.EQ("_t", "Cat");
var cursor = collection.FindAs<Animal>(query);
Note in the latter case I have to deal with discriminator ("_t") and hardcode my class name, that is not quite convenient and looks awful. If I miss the query I got an exception on enumeration attempt. Have I missed something? My suggestion was the document Db which stores objects 'as is' should handle inheritance easily.
Assuming your discriminators are functioning (_t is stored correctly for each document) then I think this is what you are looking for.
var results = collection.AsQueryable<Animal>().OfType<Cat>
Returns only those documents that are of type 'Cat'.
Well, a document db does in fact store objects "as is" - i.e. without the notion of objects belonging to some particular class. That's why you need _t when you want the deserializer to know which subclass to instantiate.
In your case, I suggest you come up with a discriminator for each subclass, instead of relying on the class name. This way, you can rename classes etc. without worrying about a hardcoded string somewhere.
You could do something like this:
public abstract class SomeBaseClass
{
public const string FirstSubClass = "first";
public const string SecondSubClass = "second";
}
[BsonDiscriminator(SomeBaseClass.FirstSubClass)]
public class FirstSubClass { ... }
and then
var entireCollection = db.GetCollection<FirstSubClass>("coll");
var subs = entireCollection.Find(Query.Eq("_t", SomeBaseClass.FirstSubClass));
From your link:
The one case where you must call RegisterClassMap yourself (even without arguments) is when you are using a polymorphic class hierarchy: in this case you must register all the known subclasses to guarantee that the discriminators get registered.
Register class maps for your base class and each one of your derived classes:
BsonClassMap.RegisterClassMap<Animal>();
BsonClassMap.RegisterClassMap<Cat>();
BsonClassMap.RegisterClassMap<Dog>();
Make sure that your collection is of type of your base class:
collection = db.GetCollection<Animal>("Animals");
Find using your query. The conversion to the corresponding child class is done automatically:
var query = Query.EQ("_t", "Cat");
var cursor = collection.Find(query);
If the only concern is hardcoding class name, you can do something like this:
collection = db.GetCollection<Animal>("Animals");
var query = Query.EQ("_t", typeof(Cat).Name);
var cursor = collection.Find(query);
Take a look on the documentation
http://docs.mongodb.org/ecosystem/tutorial/serialize-documents-with-the-csharp-driver/#scalar-and-hierarchical-discriminators
The main reason you might choose to use hierarchical discriminators is because it makes it possibly to query for all instances of any class in the hierarchy. For example, to read all the Cat documents we can write:
var query = Query.EQ("_t", "Cat");
var cursor = collection.FindAs<Animal>(query);
foreach (var cat in cursor) {
// process cat
}

Resources