mongoose query: find an object by id in an array - arrays

How could I find an image by id in this Schema. I have the id of the User and the id of the image I am looking for. What would be the best way to do this and do all images in this case have different ids or could they have the same id because they don't belong to the same User?
My Schema looks like this:
var userSchema = new Schema({
local: {
email: String,
password: String
},
facebook: {
id: String,
token: String,
email: String,
name: String
},
name: String,
about: String,
images: [{
id: Schema.ObjectId,
link: String,
main: Boolean
}]
});

When you are interested in the full object it is a simple find:
.find({"facebook.id":"<id>", "images.id":<image-id>})
I don't think that there is a way to reduce the image array in the result.
To update a single element in the image array you can use this:
.update({"facebook.id":"<id>", "images.id":<image-id>}, {$set : {"images.$.main" :false} } );

userSchema .find({facebook.id: "some ID",{ "images.id": { $in: [ id1, id2, ...idn] }}
since images are inside the document you can have same ID's however every time you query you should keep in mind that you send some other parameters such as facebook.id or facebook.email along with image id's to retrieve them. Otherwise you end up getting all that might be irrelevant only because you decide to keep same ID's for images.

tl;dr
I struggled with this and came up with a solution. Like you, I was trying to query for a deeply nested object by the _id, but I kept coming up empty with the results. It wasn't until I did some type checking that I realized the id value I was getting from my frontend, while directly supplied by mongoose, was in fact a String and not an Object.
I realize this question was already partially answered before, but that person's solution didn't work for me, and the comment on the answer tells me you wanted to update the specific image you queried for, which is exactly what I was trying to do.
The solution
In order to select an object from the nested array by the _id value, first you'll have to install the npm package bson-objectid and use the provided method to convert your string into an objectId in your query.
In your terminal:
npm i bson-objectid
In your code:
const ObjectId = require('bson-objectid')
userSchema.findOneAndUpdate(
{ "facebook.id": <user-id>, "images._id": ObjectId(<image-id>) },
{ "$set": { "images.$.main": false } },
{ new: true }, // an extra options parameter that returns the mutated document
(err, user) => {
if (err) {
handleErr(err)
} else {
console.log(user)
// do something with new user info
}
)

Related

Next js with Prisma: Upsert based on two conditions

I'm trying to do the following using Prisma:
If a category row with the same hash and user_id I have exists, just update it's "name" field, otherwise, create the row
Is this possible? TS is giving me an error saying that the type of the key given on "where" has to be of categoriesWhereUniqueInput, yet neither hash nor user_id are unique, they can repeat, it's the combination between the two that's gonna be unique
How can I work around this? Do I have to manually check if there's an id and update/create based on that?
Thanks a lot in advance!
const category = await prisma.categories.upsert({
where: {
hash,
user_id: id,
},
update: {
name,
},
create: {
hash,
name,
user_id: id,
},
});
When a combination of fields is unique, Prisma will generate a new type for the where condition which is basically just the name of all the relevant fields appended together with _.
Take a look at the categoriesWhereUniqueInput to see what the name is. Most likely it is called user_id_hash or hash_user_id.
This is what the query will look like, roughly speaking:
const category = await prisma.categories.upsert({
where: {
user_id_hash: { // user_id_hash is the type generated by Prisma. Might be called something else though.
user_id: 1,
hash: "foo"
}
},
update: {
name: "bar",
},
create: {
hash: "foo",
name: "bar",
user_id: 1,
},
})

Pushing onto Mongo SubDoc of SubDoc array

I'm going around in circles with this one so hoping someone can help. I'm building a nodejs application that receives sensor values from nodes. There can be multiple sensors on a node.
Using NodeJS, Mongod DB and Mongoose, all running on a raspberry pi, 3 I've built the following Schemas & Model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var valueSchema = new Schema ({
timestamp: {type: Date},
value: {}
});
var sensorSchema = new Schema ({
id: {type: Number},
type: {type: String},
description: {type: String},
ack: {type: Boolean},
values: [valueSchema]
});
var SensorNode = mongoose.model('SensorNode', {
id: {type: Number, required: true},
protocol: {},
sensors: [sensorSchema]
});
I can add in the node, and push sensors onto the sensors array, but I seem unable to push values onto the values array.
I've looked over a few other examples and questions on similar issues, and looked at using populate, but cant seem to get them to work.
Here is my code:
function saveValue(rsender, rsensor, payload) {
var value = {
values: {
timestamp: new Date().getTime(),
value: payload
}
}
SensorNode.findOneAndUpdate({
"id": rsender,
"sensors.id": rsensor
}, {
"$push": {
"sensors.$": value
}
}, function(err, result) {
if (err) {
console.log(err);
}
console.log(result);
});
}
This is returning undefined for the result and this error:
MongoError: exception: Cannot apply $push/$pushAll modifier to non-array
Values is definitely an array in the sensor schema.
I'm using readable ids rather than the auto assigned Mongo DB IDs for the sake of the UI, but I could use the MongoDB _id if that makes any difference, I don't see why it would?
Where am I going wrong ?
You're using positional operator $ so let's check the docs
The positional $ operator identifies an element in an array to update without explicitly specifying the position of the element in the array. To project, or return, an array element from a read operation, see the $ projection operator.
So sensors.$ will return one particular document from your sensors array. That's why you're getting an error. On this level of your document you can only replace this item by using $set. I bet you wanted to do something like this:
SensorNode.findOneAndUpdate({
"id": rsender,
"sensors.id": rsensor
}, {
"$push": {
"sensors.$.values": payload
}
});
This operation will just append payload to values array in one particular sensor with id equal to rsensor.

Mongoose - Remove several objects from an array (not exact match)

I have a collection Playlist that contains an array of items
{
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [
{
id: { // do not mix up with _id, which is the autogenerated id of the pair {id,type}. ID is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
}
]
}
Mongo automatically adds the _id field to the items when I push a pair {id,type} to items (but I don't care about it).
Now I would like to remove several "pairs" at once from the items array.
I have tried using $pullAll but it requires an exact match, and I do not know the _id, so it does not remove anything from items
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
I have tried using $pull with different variants, but it removed ALL objects from items
playlistModel.update({userId:userId},{$pull:{items:{"items.id":{$in:["123","456"]}}}},null,function(err){
playlistModel.update({userId:userId},{$pull:{items:{$in:[{id:"123",type:"video"},{id:"456",type:"video"}]}}},null,function(err){
Am I missing something or am I asking something that isn't implemented?
If the latter, is there a way I can go around that _id issue?
OK I found a way that works using $pull:
playlistModel.update({userId:userId},{$pull:{items:{id:{$in:["123","456"]}}}},null,function(err){
It doesn't take the type into account but I can't see any issue with that since the id is unique across all types anyway.
Although I will wait a bit to see if someone has a better solution to offer
EDIT
With Veeram's help I got to this other solution, which IMO is more elegant because I don't have _ids that I don't need in the database, and the $pullAll option seems more correct here
var playlistItemSchema = mongoose.Schema({
id: { // do not mix up with autogenerated _id. id is itemId
type : Schema.Types.ObjectId
},
type: {
type : String
}
},{ _id : false });
var schema = new Schema({
userId: {
type : String,
required : true,
index : true,
unique : true
},
items: [playlistItemSchema]
});
playlistModel.update({userId:userId},{$pullAll:{items:[{id:"123",type:"video"},{id:"456",type:"video"}]}},null,function(err){
tips:
you can use _id field to handle your playlistModel data.
mongoose api : new mongoose.Types.ObjectId to generate an Object_id
let _id=new mongoose.Types.ObjectId;
playlistModel.updateMany({_id:_id},{ $set: { name: 'bob' }}).exec(data=>{console.log('exec OK')});

Mongoose query array of ObjectId's for one ObjectId

Trying to query a subdocument array which contains ObjectId refs, and find one item.
The items are ObjectId's and get populated when I add a new lesson and 'assign' it to a specific location.
This mongoose schema shows what i mean.
var mongoose = require('mongoose');
var lessonNames = new mongoose.Schema({
day: {type: String },
name: {type: String},
startTime: {type: String},
endTime: {type: String}
});
var locationNames = new mongoose.Schema({
name: {type: String, required: true},
address: String,
lessons: [ { type: mongoose.Schema.Types.ObjectId, ref: 'lessonnames'}]
});
mongoose.model('lessonnames', lessonNames);
mongoose.model('locationnames', locationNames);
I have added a lesson to one location, and now i'm trying to find this specific lesson. I've tried a couple of methods, but none worked for me. Every time i get a null or undefined reported when i output the query to the console.log.
if (location.lessons && location.lessons.length > 0) {
loc
.findById(req.params.locationid)
.populate('lessons', '_id')
.exec(function (err, myLesson) {
console.log(myLesson.lessons)
});
When i execute the above statement, i see the following being generated in the nodejs console, with mongoose debugging enabled.
Mongoose: locationnames.findOne({ _id: ObjectId("56d4b687c4bcb5681a870cb5") }) { fields: undefined }
GET /api/locations/56d4b687c4bcb5681a870cb5/lesson/56d4b687c4bcb5681a870cb4
Mongoose: lessonnames.find({ _id: { '$in': [ ObjectId("56d4b687c4bcb5681a870cb4") ] } }) { fields: { _id: 1 } }
The code line below is the output from console.log. As you can see, the ObjectId is shown as part of the lessons array. But how do I make a query which 'selects' this ObjectId, so i can reference with it to a lesson.
[{"_id":"56d4b687c4bcb5681a870cb4"}]
Search the web, and saw some posts about the _id nog being a string type, and i should cast it to be a string. But i think it should be possible with ObjectId and it should be rather easy to query for it, but i lack good knowledge of mongoose and mongodb to get it working.
Any help would be appreciated!
Please try it as below, the {_id: "56d4b687c4bcb5681a870cb4"} in populate to match the ids in lessons array.
loc.findById(req.params.locationid)
.populate('lessons', null, {_id: "56d4b687c4bcb5681a870cb4"})
.exec(function (err, location) {
});

mongodb find single embedded document as an object not as an array

I wonder if there is a way to get a single object instead of an array when querying for a single embedded document in MongoDB
I have Groups with embedded Users
{
groupname: "Admins",
users: [
{
email: bob#google.com,
first_name: 'bob'
},
{...},
{...} // multiple embedded users
]
}
I can query a single user from a group with this query
db.groups.find({'users.email' => bob#google.com}, {'users.$' => 1})
but it gives me a 'users' array with 1 user init
{
groupname: "Admins",
users: [
{
email: bob#google.com,
first_name: 'bob'
}
]
}
then I have to select the first element in the array,
users[0]
there is no problem with it, but then i just have to write more code in my application, the better way should be
user (-s)
so I can query
user.first_name
if someone knows a way let me know
You can use findOne as it returns a single document, where find returns a cursor.
>user = db.groups.findOne({'users.email' : bob#google.com}, {'users.$' => 1})
>user.first_name
Depending from the driver you are using findOne is deprecated, you should use find().limit(1).next(function(err, doc){})
http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html#findOne

Resources