Dynamically add custom instance methods to a mongoose schema - javascript

Is there a way to add custom instance methods to a mongoose schema after it has already been "exported".
For example, if I have a schema:
module.exports = function(app, db, config) {
var MySchema = new Schema({ name: String });
MySchema.methods.doIt = function() { console.log("I DID IT!"); }
db.model("MySchema", MySchema);
}
Then I want to add new methods to the schema dynamically after it has already been loaded into the mongoose model object.
MySchema = db.model('MySchema');
var obj = new MySchema({name: "robocop"});
var myNewMethod = function() { console.log(this.name); }
// Do Magic here to add the myNewMethod to object.
obj.myNewMethod();
Did You Even Try?
I have already tried to just add it to the mongoose model object, however this produces errors saying the schema objects do not have the method I just added.
MySchema = db.model('MySchema');
MySchema.schema.methods.myNewMethod = function() { console.log(this.name); }
db.model('MySchema', MySchema);
console.log(MySchema.schema.methods); // This shows my method was added!
...
var obj = new MySchema({name: "robocop"});
obj.myNewMethod(); //ERROR: YOUR METHOD DOESN'T EXIST!

Caveat coder. It is possible, and whether or not it's a good idea is left as an exercise for the reader.
Your schema is of course affected by the schema object, not any particular instance of your model. Thus, if you want to modify the schema, you'll need to have access to the schema itself.
Here's an example:
var mongoose = require('mongoose')
, db = mongoose.connect("mongodb://localhost/sandbox_development")
var schema = new mongoose.Schema({
blurb: String
})
var model = mongoose.model('thing', schema)
var instance = new model({blurb: 'this is an instance!'})
instance.save(function(err) {
if (err) console.log("problem saving instance")
schema.add({other: String}) // teh secretz
var otherInstance = new model({blurb: 'and I am dynamic', other: 'i am new!'})
otherInstance.save(function(err) {
if (err) console.log("problem saving other instance", err)
process.exit(0)
})
})
Notice the call to schema.add which Schema calls internally when you make a new one.

Related

Accessing the name of a class as a string in JavaScript when class was created using another constructor [duplicate]

I´m using mongoose and I need to find a model name from a model instance.
In one part of the code I have:
const schema = new mongoose.Schema({
name: {
type: String,
required: true
},
phone: {
type: String,
required: true
}
}
const schema = new mongoose.Schema('MyData', schema);
let instance = new this({
name: 'Pete',
phone: '123'
});
Ths instance variable is passed around in my code. Later I need to find out instance name, but I´m no sure if there is a way to do it, something like:
let instanceName = getInstanceName(instance); <== Expects 'MyData' to be returned
Is that possible using mongoose ?
I realized I had a model not an instance of a model so I needed to use something else.
If you have a model, you can get the name as below:
const model = mongoose.model("TestModel", schema);
const collectionName = model.collection.collectionName;
If you have a specific item/instance of the model:
const instance = new model({...});
const collectionName = instance.constructor.modelName
as Hannah posted.
The name of the model can be accessed using this instance.constructor.modelName.
In my case I was looking for how to get a discriminator model name from a mongoose model, and the suggested solutions didn't work:
const PerformanceResult = DropDown.discriminator('PerformanceResult', new db.Schema({
name: { type: String, required: true }
}))
export default PerformanceResult
console.log(PerformanceResult.constructor.modelName) // undefined
console.log(PerformanceResult.collection.collectionName) // DropDown (parent name)
you can use this:
console.log(PerformanceResult.modelName) // PerformanceResult
mongoose version: "^5.11.8"

Mongo/Mongoose JS — Update document to latest state?

Is there a way to store a document then get an updated value of it at a later date without needing to query and populate again?
const someDocument = await SomeModel.findOne({...}).populate(...);
// store a reference to it for use
const props = {
document: someDocument,
...
}
// at a later time
await props.document.getLatest() <-- update and populate in place?
As describe here,
You can define your own custom document instance methods by add functions on the schema level.
example taken from mongoose doc:
// define a schema
const animalSchema = new Schema({ name: String, type: String });
// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
return mongoose.model('Animal').find({ type: this.type }, cb);
};
const Animal = mongoose.model('Animal', animalSchema);
const dog = new Animal({ type: 'dog' });
dog.findSimilarTypes((err, dogs) => {
console.log(dogs); // woof
});
This is the only way I can think of doing what you want - add a function that populate.
another thing that might help: you can create a pre hook on 'find' and add populate in it

Typescript Set collection field inside object before HTTP POST

Before trying to POST an Object (User), I got this error when composing it like this:
Here is my model class:
export class User {
userRoles: Set<UserRole>;
id: number;
}
In my TS I put:
const newUser = new User();
var userRoleList = new Set<UserRole>();
var iterator = userRoleList .values();
newUser.userRoles= Array.from(iterator);
I got this error:
error TS2740: Type 'UserRole[]' is missing the following properties
from type 'Set': add, clear, delete, has, and 2 more.
newUser.userRoles= Array.from(iterator);
Any ideas? Thanks.
You are trying to assign a value of type Array to the property of a class with the type Set. Change your class declaration to:
export class User { userRoles: UserRole[]; id: number; }
You can pass the array to the Set constructor :
const newUser = new User();
var userRoleList = new Set<UserRole>();
var iterator = userRoleList .values();
newUser.userRoles= new Set(Array.from(iterator));
Done in a more elegant way:
const newUser = new User();
const userRoleList = new Set<UserRole>();
newUser.userRoles= new Set([...userRoleList .values()]);

Sequelize not returning instance of model

The sequelize documentation (http://docs.sequelizejs.com/en/v3/docs/raw-queries/) states:
If you pass a model the returned data will be instances of that model.
// Callee is the model definition. This allows you to easily map a query to a predefined model
sequelize.query('SELECT * FROM projects', { model: Projects }).then(function(projects){
// Each record will now be a instance of Project
})
I have defined a model for a resource called agent.
module.exports = function(sequelize, DataTypes) {
let Agent = sequelize.define('Agent', {
responseStatus: DataTypes.STRING,
agentnum: {
type: DataTypes.STRING,
primaryKey: true,
allowNull: false,
field : 'agentno'
},
fname : {
type: DataTypes.STRING,
allowNull : false,
field: 'fname'
},
lname : {
type: DataTypes.STRING,
allowNull: false,
field : 'lname'
},
fullname : {
type: DataTypes.STRING,
allowNull : false,
field: 'full_name'
},
status : {
type: DataTypes.STRING,
allowNull: false,
field: 'business_status'
},
loginDate: DataTypes.DATE
}, {
freezeTableName: false,
timestamps: false
});
return Agent;
};
And when calling sequelize.query with my query and specifying model:Agent, I get an error thrown from sequelize:
TypeError: this.model.bulkBuild is not a function
the stack points to sequelize\lib\dialects\abstract\query.js:675.
This error persists until I apply a QueryType of sequelize.QueryTypes.RAW. At this point the query completes and I get a JSON response but it is not an instance of my Agent model. The JSON response from the sequelize query contains field names that should be mapped to.
I have imported my model (its just one) according to the directions found in their express sample (https://github.com/sequelize/express-example/blob/master/models/index.js). The models collection shows that my Agent model is included.
import Sequelize from 'sequelize';
import config from './config';
export default callback => {
const sequelize = new Sequelize(config.database, config.username, config.password, config.params);
sequelize.sync().then(function() {
let db = { }
let agentModel = sequelize.import('model/agent.js');
db[agentModel.name] = agentModel;
db.sequelize = sequelize;
db.Sequelize = Sequelize;
db.sequelize.authenticate().then(function() {
console.log('CONNECTION OK');
});
callback(db);
}).catch(function(err) {
console.log('FAILED TO CONNECT: ', err.message);
});
}
I want the query to return an instance of Agent when that query is run (invoked from a POST to my api). I am using MS SQL Server 2008 R2.
Any input is appreciated. Thanks.
EDIT 1/30 Here is the code generating the sequelize object, and passing in the model. The model collection shows my item is added, but it has no properties.
connectDb: (function () {
var sequelize;
function createInstance() {
var sequelizeInstance, connectedAndAuthenticated;
sequelizeInstance = new Sequelize(config.database, config.username, config.password, config.params);
connectedAndAuthenticated = sequelizeInstance.authenticate();
connectedAndAuthenticated.sequelize = sequelizeInstance;
connectedAndAuthenticated.Sequelize = Sequelize;
var model = sequelizeInstance.import('../src/model/agent.js');
return connectedAndAuthenticated;
}
return {
getInstance : function () {
if (!sequelize) {
sequelize = createInstance();
}
return sequelize;
}
};
}())
EDIT 1/26 After manipulating the QueryTypes, I discovered two things - that I inadvertently created a table in the database with the name of the model (Agent), and that the object returned has a tablename property value of empty. The schema and tablename are specified by me, but the query, being a stored procedure that joins a number of queries and tables, does not directly map to an object in my database named Agent. That being said, the documentation to me seems to suggest that this does not and should not matter, as I am creating my own model that is bound to the query result.
sequelize doc is confusing .i'm explaining you clean way to use sequelize
put
var models = require('../models');
in your code file be sure models directory contain index.js as you told me in your question and also Project model. be careful, other then correctly configured model there must not anything.
Now put
models.sequelize.query("select 1 as val").then(function(result){
console.log(result)
})
in your code
to check connection also you should use find query like
models.Projects.findAll().then(function(result){
console.log(result)
})
It seems like a simple typo. I don't think Agent is actually defined in your scope. I think you should be passing agentModel or whatever you bound the import to.
let agentModel = sequelize.import('model/agent.js');
db.sequelize.query( "DECLARE #response VARCHAR(256); EXEC API_Login #agentnum = N'" + agentNum + "', #hashedPassword = '" + password + "', #response = #response OUTPUT; SELECT #response AS N'response'",{ model: agentModel, type: sequelize.QueryTypes.RAW}) .spread(function(Agent) { res.status(200).json(Agent); }) .catch(function(err) { handleError(err, res); });
Note I'm using {model: agentModel, ...} not {model: Agent, ...} because Agent is undefined outside of the callback.
Your error TypeError: this.model.bulkBuild is not a function makes perfect sense if Agent is not actually a model but something else (or undefined).
UPDATE
You mention in comments on the post below that: "I have synced the model - the query attempts to create a table, instead of binding to the passed in agent model" and "It should not be creating a table".
Why do you think that is that is the case? Creating the table during sync() is normal behaviour for Sequelize.
I think you misunderstand how Sequelize works. It creates a table for every registered model during sync. If it can't create that table it might be returning an invalid model and that is why you are getting errors.
Models are explicity tied to individual database tables, that's the fundamental behavior of Sequelize. Each model instance represents a row of that table. If you are working with stored proceedures then you are probably better off using using the native database library and defining your own abstraction layer.
I'm sure you can disable and/or override all the default synchronization between the model and the underlying DB table but at a certain point or complexity you'd have basically just written your own abstraction library and it would be cleaner to just do it from scratch.
Maybe you can use Sequelize the way you want but at the very least you should not be calling sync(). What side-effects that leads to I can't say but I can say that unless you define your own beforeSync hook sync() will always create a table in your schema:
Model.prototype.sync = function(options) {
options = options || {};
options.hooks = options.hooks === undefined ? true : !!options.hooks;
options = Utils._.extend({}, this.options, options);
var self = this
, attributes = this.tableAttributes;
return Promise.try(function () {
if (options.hooks) {
return self.runHooks('beforeSync', options);
}
}).then(function () {
if (options.force) {
return self.drop(options);
}
}).then(function () {
return self.QueryInterface.createTable(self.getTableName(options), attributes, options, self);
})
...
}).return(this);
};

Is there a more elegant way to "fake" class inheritance?

I have not found an easy way to extend Mongoose Schema/Model methods because of the way that mongoose handles them, and because of the fact that mongoose=require('mongoose') is a singelton.
So, I am 'faking' class inheritance here:
'use strict';
var _ = require('lodash');
module.exports = function(MongooseModel, options) {
var Collection = {};
_.assign(Collection, _.toPlainObject(MongooseModel));
Collection.pluralName = Collection.modelName + 's';
Collection.foo = Collection.bar;
return Collection
};
Does anyone have a more elegant solution?
EDIT:
Turns out the above solution doesn't work. For instance, using Collection.find({}, function(err, docs) {...}) will error when Mongo tries to create "docs" from a model that has not been registered with Mongoose.
So, what I've done is now completely inelegant:
'use strict';
var _ = require('lodash');
module.exports = function(MongooseModel, options) {
var Collection = MongooseModel;
...
return Collection
};
There are some ways to try and do this, though not sure exactly what your trying to extend.
You can add instance methods <schema>.methods.<mymethod> = function(){}
// define a schema
var animalSchema = new Schema({ name: String, type: String });
// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function (cb) {
return this.model('Animal').find({ type: this.type }, cb);
}
And you can add static methods <schema>.statics.<mymethod> = function(){}
// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function (name, cb) {
return this.find({ name: new RegExp(name, 'i') }, cb);
}
var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function (err, animals) {
console.log(animals);
});
Examples are from the mongoose docs - just search for "statics".
The statics functions you can call on a model. The methods are usually functions that work with an instance of a document returned from a query or created with new.

Categories

Resources