Mongoose get all model instance fields [duplicate] - node.js

I'm using Mongoose.js to create models with schemas.
I have a list of models (many) and at times I'd like to get the attributes/keys that make up a particular model.
Is there a method to pull out the attribute schemas for any given model?
For example,
var mySchema = module.exports = new Schema({
SID: {
type: Schema.Types.ObjectId
//, required: true
},
teams: {
type: [String]
},
hats: [{
val: String,
dt: Date
}],
shields: [{
val: String,
dt: Date
}],
shoes: [{
val: String,
dt: Date
}]
}
);
Is it possible to pull out/identify the attributes of the schema [SID, hats, teams, shields, shoes]??

Yes, it is possible.
Each schema has a paths property, that looks somewhat like this (this is an example of my code):
paths: {
number: [Object],
'name.first': [Object],
'name.last': [Object],
ssn: [Object],
birthday: [Object],
'job.company': [Object],
'job.position': [Object],
'address.city': [Object],
'address.state': [Object],
'address.country': [Object],
'address.street': [Object],
'address.number': [Object],
'address.zip': [Object],
email: [Object],
phones: [Object],
tags: [Object],
createdBy: [Object],
createdAt: [Object],
updatedBy: [Object],
updatedAt: [Object],
meta: [Object],
_id: [Object],
__v: [Object]
}
You can access this through an model too. It's under Model.schema.paths.

Don't have enough rep to comment, but this also spits out a list and loops through all of the schema types.
mySchema.schema.eachPath(function(path) {
console.log(path);
});
should print out:
number
name.first
name.last
ssn
birthday
job.company
job.position
address.city
address.state
address.country
address.street
address.number
address.zip
email
phones
tags
createdBy
createdAt
updatedBy
updatedAt
meta
_id
__v
Or you could get all Attributes as an Array like this:
var props = Object.keys(mySchema.schema.paths);

My solution uses mongoose model.
Schema attributes
const schema = {
email: {
type: String,
required: 'email is required',
},
password: {
type: String,
required: 'password is required',
},
};
Schema
const FooSchema = new Schema(schema);
Model
const FooModel = model('Foo', FooSchema);
Get attributes from model:
Object.keys(FooModel.schema.tree)
Result:
[
'email',
'password',
'_id',
'__v'
]

Solution for lodash, function which picked all schema properties, excluding specified
_.mixin({ pickSchema: function (model, excluded) {
var fields = [];
model.schema.eachPath(function (path) {
_.isArray(excluded) ? excluded.indexOf(path) < 0 ? fields.push(path) : false : path === excluded ? false : fields.push(path);
});
return fields;
}
});
_.pickSchema(User, '_id'); // will return all fields except _id
_.pick(req.body, _.pickSchema(User, ['_id', 'createdAt', 'hidden'])) // all except specified properties
read more here https://gist.github.com/styopdev/95f3fed98ce3ebaedf5c

You can use Schema.prototype.obj that returns the original object passed to the schema constructor. and you can use it in a utility function to build the object you're going to save.
import Todo from './todoModel'
import { validationResult } from 'express-validator'
const buildObject = (body) => {
const data = {};
const keys = Object.keys(Todo.schema.obj);
keys.forEach(key => { if (body.hasOwnProperty(key)) data[key] = body[key] })
return data;
}
const create = async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.json(errors);
let toBeSaved = buildObject(req.body);
const todo = new Todo(toBeSaved);
const savedTodo = await todo.save();
if (savedTodo) return res.json(savedTodo);
return res.json({ 'sanitized': keys })
} catch (error) {
res.json({ error })
}
}
another way is to to not call the buildObject function and add it in two lines but you will write every key you want to save
let { title, description } = req.body;
let toBeSaved = { title, description };
Using ES6 shorthand property names

If you want to have only the attributes you added and not the add methods by the ORM that starts with '$___', you have to turn the document into object then access the attributes like this:
Object.keys(document.toObject());

The accepted answer did not work for me.
But using Mongoose 5.4.2 I was able to get the keys by doing the following:
const mySchema = new Schema({ ... });
const arrayOfKeys = Object.keys(mySchema.obj);
I'm using typescript, however. That might have been the problem.

In case you want to have all property values (including nested and populated properties), just use toObject() method:
let modelAttributes = null;
SomeModel.findById('someId').populate('child.name').exec().then((result) => {
modelAttributes = result.toObject();
console.log(modelAttributes);
});
The output would be:
{
id: 'someId',
name: 'someName',
child: {
name: 'someChildName'
}
...
}

Just insert the field name you like to get.
let fieldName = 'birthday'
console.log(mySchema.schema.paths[fieldName].instance);

Iterate over keys aka attributes
for (var key in FooModel.schema.obj) {
//do your stuff with key
}

Related

Update document inside array of objects in mongoose

I need to update objects inside an array so I'm trying but I get the following error:
error Plan executor error during findAndModify :: caused by :: The
positional operator did not find the match needed from the query.
This is my code:
const payment = await Purchase.findByIdAndUpdate(
{ '_id': req.body.id, 'payments._id': req.body.paymentId },
{
$set: {
'payments.$.status': false
}
}
,{ new: true });
payments object on Model:
payments: [
{
createdBy: [Object],
createdAt: '08/13/22',
paymentNumber: 0,
previousBalance: 3747.68,
paymentAmount: 3747.68,
outstandingBalance: 0,
status: true,
_id: new ObjectId("62f83f3c22e4f67dde8cb85a"),
lastModificationBy: [],
disabledBy: []
}
]
while using fineByIdAndUpdate you only need to pass id of document to be updated.
const payment = await Purchase.findByIdAndUpdate(req.body.paymentId,{status:false},{new:true} )
for using findByIdAndUpdate, you need to add this runValidators
const payment = await Purchase.findByIdAndUpdate(req.params.id,
'payments.$.status': false
}, {runValidators: true}
while same update can be done by this as well
const payment = await Purchase.findByIdAndUpdate(
req.body.paymentId,
{
status:false
}, {
new:true
}
)

Parsing posted object array, Express

I need to parse data with Express from form:
invoiceRouter.post('/', async (req,res) => {
console.log(req.body);
let invoice = new Invoice();
invoice = req.body;
invoice.status = 0;
//save
res.redirect('/invoices');
});
When I log, the array of objects is read as list of values:
{
createdDate: '2021-10-15',
invoiceRows: [ 'Title3', '2', 'Title2', '3' ]
}
But it can not read the invoiceRows as array of 2, therefore I am struggling to parse it into array for saving it.
When I set the extended: false, I can see following result from req.body:
[Object: null prototype] {
createdDate: '2021-10-15',
'invoiceRows[0].productTitle': 'Title2',
'invoiceRows[0].unitPrice': '2',
'invoiceRows[1].productTitle': 'Title3',
'invoiceRows[1].unitPrice': '2'
}
The schema I am using:
const invoiceSchema = new mongoose.Schema({
createdDate: {
type: Date,
required: true
},
status: {
type: Number,
required: true
},
invoiceRows: [{
productTitle: String,
unitPrice: Number
}]
});
Question: what am I doing wrong, in order to get array of objects from req.body inside parent object?
In your req.body you should be receiving like bellow (As per your model schema). Make your front end to send data like bellow.
{
createdDate: '2021-10-15',
invoiceRows: [ { productTitle :'Title1', unitPrice : 2}, { productTitle :'Title2', unitPrice : 3} ]
}

Filtering documents by objects in an array

I have an array of strings in my schema, and I'm trying to filter documents depending on their arrays containing certain strings or not. Below is my schema, where the ingredients array is what I'm trying to filter by:
const foodSchema = mongoose.Schema({
name: {
type: String,
required: true,
trim: true,
},
ingredients: [
{
type: String,
required: true,
trim: true,
},
],
});
In nodejs, I have the following inside my express router:
router.get('/food', auth, async (req, res) => {
const match = {};
if (req.query.name) {
match.name = req.query.name;
}
if (req.query.ingredients) {
match.ingredients = req.query.ingredients;
}
try {
const allFood = await Food.find(match);
res.send({
allFood,
});
} catch (error) {
res.status(400).send(error);
}
});
Here's an example request being sent:
{{url}}/food?ingredients=Salmon Meal&ingredients=Deboned Turkey
I would expect to get all food documents where their ingredients array contain both Salmon Meal and Deboned Turkey, but I always get an empty array. The casing is correct. If I just use one ingredient in the query, it works fine.
Use $all
match.ingredients = { $all: req.query.ingredients };
https://docs.mongodb.com/manual/reference/operator/query/all/
Use $in
if (req.query.ingredients && req.query.ingredients.length > 0) {
match.ingredients = { $in: req.query.ingredients };
}

Sequelize: Virtual column is not returned in query results

I can't get this very simple virtual column to work (surnameName). It is not returned in query results, but it does not throw any error either.
My model (I removed irrelevant fields):
const Person = connectionPool.define('person', {
ID: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true
},
name: Sequelize.STRING,
surname: Sequelize.STRING,
surnameName: {
type: Sequelize.VIRTUAL(Sequelize.STRING, ['surname', 'name']),
get() {
return this.getDataValue('surname') + ' ' + this.getDataValue('name');
}
}
});
This is how I query the model:
const cfg = {
where: {},
limit: 10,
raw: false, // tried with and without this line
attributes: ['surnameName']
}
models.Person.findAll(cfg)
.then(results => {
console.log(results[0]);
})
And this is what I get in the console log:
person {
dataValues: { surname: 'Baggins', name: 'Frodo' }, // all other fields are autoexcluded by Sequelize
...
_options:
{ isNewRecord: false,
_schema: null,
_schemaDelimiter: '',
raw: true, // is true even if I set 'raw' to false in findAll options
attributes: [ 'surnameName', 'surname', 'name' ] // <= surnameName is there!
}
}
Virtual column is not returned in the results, however the logged instance shows that the internal _options.attributes array does contain the field, so Sequelize somehow acknowledges that it should be added. I tried explicitly turning raw=false, as I read that raw excludes virtual columns, but it has no effect. The results are definitely not raw.
What can be wrong here? Any help will be appreciated!
It is possible to hide properties javascript object. Here is an example
function Person(fName, lName) {
this.fName = fName;
this.lName = lName;
Object.defineProperties(this, {
fullName: {
get : function () {
return this.fName + " " + this.lName;
}
}
});
}
const ratul = new Person("Ratul", "sharker");
console.log(ratul);
console.log(ratul.fullName);
Look closely that console.log(ratul) does not print fullName, but fullName is sitting here, returning it's value seen in console.log(ratul.fullName).
Similar thing can be found in this answer.

Mongoose — findOneAndUpdate() causing document duplication

I'm using findOneAndUpdate() with upsert: true in order for a document to be updated if it exists and to be created otherwise. The tracks variable contains an array of Track instances. tracks does contain a few duplicates and that's where the problem begins. It causes the piece of code on line 7 (Observation.findOneAndUpdate(...)) to create a (low) number of duplicates, i.e. multiple documents that have the same (user, track) pair. Note that those duplicates are inserted randomly: running twice this piece of code brings different duplicated documents. My guess is that it has something to do with how the locking of data is done in MongoDB and that I'm doing too many operations at the same time. Any idea on how I could overcome this problem?
const promises = [];
​
tracks.forEach((track) => {
const query = { user, track };
const options = { new: true, upsert: true };
const newOb = { user, track, type: 'recent' };
promises.push(Observation.findOneAndUpdate(query, newOb, options));
});
​
return Promise.all(promises);
I'm using mongoose 5.5.8 and node 11.10.0.
Here's the Observation model:
const { Schema } = mongoose;
const ObservationSchema = new Schema({
track: { type: Schema.Types.ObjectId, ref: 'Track' },
user: { type: Schema.Types.ObjectId, ref: 'User' },
type: String
});
ObservationSchema.index({ track: 1, user: 1 }, { unique: true });
const Observation = mongoose.model('Observation', ObservationSchema);
And this is a sample of what the tracks array contains:
[
{ artists: [ 5da304b140185c5cb82d7eee ],
_id: 5da304b240185c5cb82d7f48,
spotifyId: '4QrEErhD78BjNFXpXDaTjH',
__v: 0,
isrc: 'DEF058230916',
name: 'Hungarian Dance No.17 In F Sharp Minor',
popularity: 25 },
{ artists: [ 5da304b140185c5cb82d7eee ],
_id: 5da304b240185c5cb82d7f5d,
spotifyId: '06dn1SnXsax9kJwMEpgBhD',
__v: 0,
isrc: 'DEF058230912',
name: 'Hungarian Dance No.13 In D',
popularity: 25 }
]
Thanks :)
I think this is due to your Promise.all method.
You should await every single query in the loop instead of awaiting everything at the same time at the end. Here an example with find:
async function retrieveApples() {
const apples = [];
arr.forEach(apple => {
const foundApple = await AppleModel.findOne({ apple });
apples.push(foundApple);
});
return apples
}

Resources