The task is to store some documents into MongoDB. The documents have the same top-level but from there they could be different.
The structure of the payload is:
{
"types": "a", //the type can be "a", "b" or "c"
"details" : {
... // the details object structure is different for each type
}
}
and this is the model I wrote:
const Details = { strict: false };
const MyOrder = new Schema({
types: {
type: String,
enum: ['a', 'b', 'c'],
},
details: Details,
});
module.exports = Order = mongoose.model('myOrder', MyOrder);
I set details with { strict: false } because I want to get its data no matter what structure it has. Maybe it's wrong something there.
When a POST request is done, the document saved into the database it looks like this:
_id: ObjectId("...")
types: "a"
__v : 0
It saved the types but nothing about the details.
Is it a way to save the details too?
I managed to solve the problem by not creating another Details object like above but adding { strict: false } inside the Schema. Like this:
const MyOrder = new Schema(
{
types: {
type: String,
enum: ['a', 'b', 'c'],
},
},
{ strict: false }
);
module.exports = Order = mongoose.model('myOrder', MyOrder);
Related
This question already has answers here:
mongodb/mongoose findMany - find all documents with IDs listed in array
(9 answers)
Closed 3 months ago.
I am trying to search using node.js, ejs and mongoose. All the filter parameters are working perfectly but only categoryIds is not (stored as a collection of ObjectIDs in the mongodb document, referring to the respective document in categories collection), always giving me the empty record set.
For example:
If I need to find the a movie called Cosmos (see the attached screenshot) then I can easily find it with all or any filter except categories. Once I select any category, the record-set will go blank even if the I have selected the one which it belongs to.
model.js
const Model = mongoose.model('Movie', new Schema({
...
categoryIds: [{
type: Schema.Types.ObjectId,
trim: true,
default: null,
ref: 'Category',
}],
copyrightId: {
type: Schema.Types.ObjectId,
trim: true,
default: null,
ref: 'Copyright',
},
...
}, {
timestamps: true
});
Controller.js
Router.get('/', (req, res) => {
const search = req.query;
const conditions = (() => {
let object = {};
['releaseYear', 'languageId', 'copyrightId'].forEach(filter => {
if (search[filter] != '') {
object[filter] = search[filter];
}
});
if (typeof search.categoryIds !== 'undefined') {
object.categoryIds = [];
search.categoryIds.forEach(item => object.categoryIds.push(item));
}
if (search.keywords != '') {
object.title = {
$regex: search.keywords,
$options: 'i'
};
}
return object;
})();
const count = await Model.count(conditions);
const items = await Model.find(conditions, {
__v: false,
imdb: false,
trailer: false,
createdAt: false,
updatedAt: false,
}).sort({
status: -1,
releaseYear: -1,
title: 1
})
.populate('languageId', ['title'])
.populate('copyrightId', ['title'])
.populate('categoryIds', ['title'])
.skip(serialNumber)
.limit(perPage);
...
});
All the fields in the search form
{
categoryIds: [
'6332a8a2a336e8dd78e3fe30',
'6332a899a336e8dd78e3fe2e',
'6332a87ba336e8dd78e3fe2c',
'634574ab339b1a6b09c1e144'
],
languageId: '',
copyrightId: '',
releaseYear: '',
rating: '',
seen: '',
status: '',
keywords: '',
submit: 'search' // button
}
filtered search parameters
{
categoryIds: [
'6332a8a2a336e8dd78e3fe30',
'6332a899a336e8dd78e3fe2e',
'6332a87ba336e8dd78e3fe2c',
'634574ab339b1a6b09c1e144'
]
}
Here is the screenshot of mongodb document.
...
if (typeof search.categoryIds !== 'undefined') {
object.categoryIds = {
$in: []
};
search.categoryIds.forEach(item => object.categoryIds.$in.push(
mongoose.Types.ObjectId(item))
);
}
console.log(object);
return object;
The is the final filter object
{
categoryIds: {
'$in': [
new ObjectId("6332a87ba336e8dd78e3fe2c"),
new ObjectId("634669f4a2725131e80d99f1")
]
}
}
Now, all the filters are working perfectly.
Thank you everyone.
The filter should contain all categoryIds and in the same order to match the document. It's not quite clear from the question if it is the intended functionality. If not, most popular usecases are documented at https://www.mongodb.com/docs/manual/tutorial/query-arrays/
I don't recall how mongoose handles types when you query with array function like $all, so you may need to convert string IDs to ObjectIDs manually, e.g.:
search.categoryIds.forEach(item => object.categoryIds.push(
mongoose.Types.ObjectId(item))
);
I've a mongoose schema called Route as the following:
const mongoose = require("mongoose");
const collection_label = "routes";
const class_label = "Routes";
const schema = mongoose.Schema(
{
createdOn: { type: Date, default: Date.now },
attachedSubTasks: [
{ type: Schema.Types.ObjectId, ref: "Subtasks", default: "" },
],
},
{ collection: class_label }
);
The fact is that sometime the field can be an empty array, but when I want to create a Route i get the following error:
ValidationError: Routes validation failed: attachedSubTasks: Cast to [ObjectId] failed for value "[""]" at path "attachedSubTasks"
Is there a way to accept the fact that this can be empty?
I tried without default, with default: "" and with default: null
You need to set default as null in your schema not ""
you have also this way to define attachedSubTasks in schema:
attachedSubTasks: {
type: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Subtasks"
}
],
default: []
}
The work around I've found for this problem can be implemented at the backend side. In the following example, I've just check whether the object's key value's length is less than one (1) if true I delete the object's key so that mongoose's schema does not take this into consideration
router.post("/", async (c, r) => {
let receivedData = c.body;
receivedData["attachedSubTasks"].length < 1? delete receivedData["attachedSubTasks"]: false
returnedValue = await mongoose.saveOne(Routes, receivedData);
statRes.status(200, r, returnedValue);
});
If someone has a better solution, don't hesitate to let me know.
Hey I was wondering how do I use findById for a schema inside an array? For example, I have the following Schema:
const GameSchema = new mongoose.Schema({
users: [
{
user: { type: mongoose.Schema.ObjectId, ref: 'User' },
role: {
type: String,
required: true,
enum: ['user', 'moderator', 'creator'],
default: 'user',
},
},
]
}]
I want to find the user with a mongoose function like findById, such as the following:
const user = await game.users.findById({ user: req.user.id })
It doesn't seem to work since users is not a mongodb model. I know I can find the user by using find() like the following:
const user = await game.users.find(
(gameUser) => gameUser.user == req.user.id
)
The only problem is that the type of gameUser and req.user.id is not the same and I can't use '==='. Is there some way to go through the array and use the mongoose function findById?
As docs explains, findById method:
Finds a single document by its _id field
So you have to use findOne() instead of findById().
Also, to return only one field from the entire array you can use projection into find.
Check this example. This query find an object by its id (i.e. user field) and return only the object, not the whole array.
db.collection.find({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})
Using mongoose you can do:
yourModel.findOne(({
"users": { "$elemMatch": { "user": 1 } }
},
{
"users.$": 1
})).then(result => {
console.log(result)
}).catch(e => {
// error
})
I have a mongoose schema:
models/profile.js
var mongoose = require("mongoose");
var passportLocalMongoose = require("passport-local-mongoose");
var profileSchema = new mongoose.Schema({
username: String,
complete: { type: Boolean, default: false },
email: { type: String, default: "" },
city: { type: String, default: "" }
}, { discriminatorKey: 'accountType' });
profileSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model('Profile', profileSchema);
That has two discriminators associated with it:
models/finder.js
var Profile = require('./profile');
var Finder = Profile.discriminator('finder', new mongoose.Schema({
position: { type: String, default: "" },
skills: Array
}));
module.exports = mongoose.model("Finder");
models/helper.js
var Profile = require('./profile');
var Helper = Profile.discriminator('helper', new mongoose.Schema({
jobTitle: { type: String, default: "" },
lastPosition: { type: String, default: "" }
}));
module.exports = mongoose.model("Helper");
I am using this within an express framework, and on one page - shown below - I want to iterate over the key/value pairs in Profile to build a table.
I would like to retain the order designated in the Schema, so that the table ordering is consistent between pages.
Is it possible to define a sort order on Schema creation?
Here's my profile.ejs file where I make the table:
<table class="table profile-display">
<tbody>
<% for(var key in profile.toObject({versionKey: false})) { %>
<% if (key != '_id') { %>
<% if (profile[key].length === 0){ %>
<tr class="incomplete-warning table-rows">
<% } else { %>
<tr class="table-rows">
<% } %>
<td class="key-text"><%=key.toUpperCase()%>:</td>
<td><%=profile[key]%></td>
</tr>
<% } %>
<% } %>
</tbody>
</table>
Please let me know if I can provide more information
You can use retainKeyOrder
By default, mongoose reverses key order in documents as a performance optimization. For example, new Model({ first: 1, second: 2 }); would actually be stored in MongoDB as { second: 2, first: 1 }. This behavior is considered deprecated because it has numerous unintended side effects, including making it difficult to manipulate documents whose _id field is an object.
Mongoose >= 4.6.4 has a retainKeyOrder option for schemas that ensures that mongoose will always keep the correct order for your object keys.
var testSchema = new Schema({ first: Number, second: Number }, { retainKeyOrder: true });
var Test = mongoose.model('Test', testSchema);
Test.create({ first: 1, second: 2 }); // Will be stored in mongodb as `{ first: 1, second: 2 }`
References:
https://github.com/Automattic/mongoose/issues/1514
https://mongoosejs.com/docs/4.x/docs/guide.html#retainKeyOrder
Every browser handles object sorts differently. I suggest returning a JSON object which maps your schema that has a sort value or such and iterates over your profile.ejs template. Then you can just map the value from mongoose output key whichever you like.
{
1 : 'username',
2 : 'type',
3 : 'complete',
4 : 'email',
5 : 'city',
6 : 'position',
7 : 'skills'
}
or an array
[
'username',
'type',
'complete',
'email',
'city',
'position',
'skills'
]
Then from that, you can just map your schema from the value of the object or from the array. I like using an array in this case as its easier to iterate over by just using the index key. It depends on your usage and purpose.
Hope it help.
UPDATE: To minimizing in hardcoding the schema twice you can create an object which has sorts with the schema value.
Code Example.
// Construct your object
var objSchema = {
obj1 : {
schema : { type: String },
order : 2
},
obj2 : {
schema : { type: String },
order : 3
},
obj3 : {
schema : { type: String },
order : 1
}
}
// Function to construct your schema object and sort array
function mapSortSchema(objs) {
var result = {
schema : {},
order : []
}
for (var obj in objs) {
result.schema[obj] = objs[obj].schema;
result.order.push({
'key': obj,
'order': objs[obj].order
});
}
result.order.sort(function(a, b) {
return a.order - b.order;
});
return result;
}
Now you have schema for mongoose and order for template.
var mapSchema = mapSortSchema(objSchema);
// You can now use this with your mongoose schema
var forMongooseSchema = mapSchema.schema;
// result {obj1 : { type: String }, obj2 : { type: String }, obj3 : { type: String }}
// You can now use this to loop through your template
var forTemplateLoop = mapSchema.order;
// result [{key: "obj3", order: 1}, {key: "obj1", order: 2}, {key: "obj2", order: 3}]
Haven't tested this in mongoose but it will give you the basic idea, you can improve the function base on your need. Hope it helps.
I have two Schema for user & todo. Every todo has an owner as a user, every user has an array of todos.
// user.js
const TodoSchema = require('./todo').TodoSchema;
var UserSchema = mongoose.Schema({
name: {
type: String,
required: true
},
todos: {
type: [TodoSchema]
}
});
module.exports.UserSchema = UserSchema;
module.exports.UserModel = mongoose.model('UserModel', UserSchema);
// todo.js
var TodoSchema = mongoose.Schema({
body: {
type: String, required: true
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: 'UserModel',
required: true
}
});
module.exports.TodoSchema = TodoSchema;
module.exports.TodoModel = mongoose.model('TodoModel', TodoSchema);
I entered data like this.
var nUser = new UserModel({
name: "Alex
)};
nUser.save().then(user => {
var t = new TodoModel({
body: "my new todo",
owner: user._id
});
t.save().then();
});
But the problem is I want to get all the todos from a specific user, something like this...What is the correct way?
UserModel.findOne({name: "Alex"})
.then(user => {
// user.todos
});
P.S.
I can do this like TodoModel.find({owner: specific_user._id}), but I want it from UserModel.
Since you're asking for the proper way of doing it, I am gonna start with your User Schema. If you want to find all the todos of a user, then putting the todo documents inside an array in the User document is not required. So you should probably remove that from your schema.
After that you can use a simple aggregation to get your desired outcome.
UserModel.aggregate([
{
$match:{
name:"Alex"
}
},
{
$lookup:{
from:"todomodels",
localField:"$_id",
foreignField:"$owner",
as:"todos"
}
}
])
this will return all the todos for that user in an array of the same name.