Doing a 1:many:many join in mongodb - arrays

On this earlier thread, some of my code worked perfectly for the scenario in question.
I want to adapt the same code for another similar scenario and I'm yet to understand what could be wrong. This time around I have a coursemodule collection, a many:many relationship collection between courses and modules which only stores coursesId and moduleId. Since the code worked perfectly, I simply copied, did a little modification and arrived at the code below:
courses(){
var theslug = FlowRouter.getParam('myslug');
var mySchoolDocs = SchoolDb.findOne({slug: theslug});
var arrayModuleSchools = ModuleSchool.find({schoolId: mySchoolDocs._id});
// Transform the array of document into an array with only the ids
var arrayModuleId = [];
arrayModuleSchools.forEach(function(moduleSchools){
arrayModuleId.push(moduleSchools.moduleId);
});
var coursetoMod = CourseModules.find({}, {moduleId: {$in: arrayModuleId}});
if (coursetoMod) {
coursesArrayIds = [];
console.log(coursetoSchool);
coursetoMod.forEach(function (courseToModules) {
coursesArrayIds.push(courseToModules.coursesId);
});
return Courses.find({_id: {$in: coursesArrayIds}}).fetch();
}
}
To be specific, only 2 modules exist in the Modules collection, with ids - xfLM9DEzhCMYQpQ32 and PTbZQ9cTG9pByFsY2. The CourseModule collection has this has docs:
{
"_id" : "iXX4unJZRNcCw9bAm",
"moduleId" : "PTbZQ9cTG9pByFsY2",
"coursesId" : "FbgcdZxADHKRBj98z",
"createdAt" : ISODate("2017-08-25T16:36:17.173Z"),
"userId" : "n5rqFSHbhm7zqADyB"
}
{
"_id" : "RAJJFjqAjGoDeNhko",
"moduleId" : "PTbZQ9cTG9pByFsY2",
"coursesId" : "ESAf6NGpZzXeioecp",
"createdAt" : ISODate("2017-08-25T16:36:17.182Z"),
"userId" : "n5rqFSHbhm7zqADyB"
}
{
"_id" : "8ceuFwZK8Qduo5J5P",
"moduleId" : "xfLM9DEzhCMYQpQ32",
"coursesId" : "KnNj4GLcyMtvF8JmB",
"createdAt" : ISODate("2017-08-25T16:38:15.368Z"),
"userId" : "n5rqFSHbhm7zqADyB"
}
At the point where I log into the console I got that the selectorId is undefined:
L…n.Cursor {collection: LocalCollection, sorter: null, matcher:
M…o.Matcher, _selectorId: undefined, skip: undefined…}_projectionFn:
(obj)_selectorId: undefined_transform: nullcollection:
LocalCollectionfields: undefinedlimit: undefinedmatcher:
Minimongo.Matcherreactive: trueskip: undefinedsorter: null__proto__:
Object_depend: (changers, _allow_unordered)_getCollectionName:
()_getRawObjects: (options)_publishCursor: (sub)constructor:
(collection, selector, options)count: ()fetch: ()forEach: (callback,
thisArg)getTransform: ()map: (callback, thisArg)observe:
(options)observeChanges: (options)rewind: ()proto: Object
view.js:30 L…n.Cursor {collection: LocalCollection, sorter: null, matcher: M…o.Matcher, _selectorId: undefined, skip: undefined…}
All I want to do is to fetch the courses attached to a specific school currently displayed via the modules.

You are using the find function the wrong way:
var coursetoMod = CourseModules.find({}, {moduleId: {$in: arrayModuleId}});
The find() function takes two parameters : myCollection.find(query, projection). When you are filtering documents by field, it must be inside the query parameter. And the projection parameter is used to chose which fields to return.
In your case, here's the parameters you are using: query: {} and projection: {moduleId: {$in: arrayModuleId}}. But it needs to be: query: {moduleId: {$in: arrayModuleId}}
So you just have to use the $in as first parameter:
var coursetoMod = CourseModules.find({moduleId: {$in: arrayModuleId}});
By the way, if you want to see directly the documents returned by the find function inside a console.log, use .fetch() :
var coursetoMod = CourseModules.find({moduleId: {$in: arrayModuleId}}).fetch();
MongoDB find function documentation: https://docs.mongodb.com/manual/reference/method/db.collection.find/

#gaetan is on the right track with the answer, you need to use the query parameter instead of the projection parameter. There are some other simplifications that can be made in your code as well using the underscore library that is packaged with Meteor.
courses() {
const slug = FlowRouter.getParam('myslug');
const schoolId = SchoolDb.findOne({ slug })._id;
const Modules = ModuleSchool.find({ schoolId });
const ModuleIds = _.pluck(Modules,'moduleId');
const coursetoMod = CourseModules.find({ moduleId: { $in: ModuleIds }});
if (coursetoMod.length) {
coursesIds = _.pluck(coursetoMod,'coursesId');
return Courses.find({ _id: { $in: coursesArrayIds }}).fetch();
}
}

Related

Mongoose doesn't create subdocument from JSON array

I'm trying to write a JSON object that contains both first-level data along with arrays into MongoDB.
What happens instead is all first-level data is stored, but anything contained in an array isn't. When logging the data the server receives, I see the entire object, which leads me to believe there's something wrong with my Mongoose code.
So for example if I send something like this:
issueId: "test1",
issueTitle: "testtest",
rows: [
{order:1,data: [object]},
{order:2,data: [object]},
]
Only the following gets stored:
issueId: "test1",
issueTitle: "testtest",
lastUpdated: Date,
I have the following model for Mongo:
//model.js
var mongoose = require('mongoose');
var model = mongoose.Schema({
issueId : String,
issueTitle : String,
lastUpdated : {type: Date, default : Date.now},
rows : [{
order : Number,
data : [
{
title : String,
text : String,
link : String,
}
]
}]
});
module.exports = mongoose.model('Model', model);
And the routing code, where I believe the problem likely is:
//routes.js
const mongoose = require('mongoose');
const Model = require('./model.js');
...
app.post('/api/data/update', function(req, res) {
let theData = req.body.dataToInsert;
console.log(JSON.stringify(theData,null,4));
Model.findOneAndUpdate(
{issueId : theData.issueId},
{theData},
{upsert: true},
function(err,doc){
if(err) throw err;
console.log(doc);
});
});
As well, here's the part of the Angular controller storing the data. I don't think there's any problem here.
pushToServer = function() {
$http.post('/api/data/update',{
dataToInsert : $scope.dataObject,
}).then(function successCallback(res){
console.log("all good", JSON.stringify(res,null,3));
}, function errorCallback(res){
console.log("arg" + res);
});
}
Look at the first question in the mongoose FAQ:
http://mongoosejs.com/docs/faq.html
Mongoose doesn't create getters/setters for array indexes; without them mongoose never gets notified of the change and so doesn't know to persist the new value. The work-around is to use MongooseArray#set available in Mongoose >= 3.2.0.
// query the document you want to update
// set the individual indexes you want to update
// save the document
doc.array.set(3, 'changed');
doc.save();
EDIT
I think this would work to update all of the rows. I'd be interested to know if it does work.
let rowQueries = [];
theData.rows.forEach(row => {
let query = Model.findOneAndUpdate({
issueId: theData.issueId,
'row._id': row._id
}, {
$set: {
'row.$': row
}
});
rowQueries.push(query.exec());
});
Promise.all(rowQueries).then(updatedDocs => {
// updated
});

Mongoose update array property

I am trying to update a document via Mongoose, but it does not update the array property.
Here's an example document:
{
"_id" : "55da477a9bfc910e38zzccf2",
"projectname" : "ASong",
"owner" : "adam",
"tracks" : [{
"name" : "Bass",
"file" : "upload/Bass.mp3",
"volume" : "0.75",
"pan" : "0.65"
}, {
"file" : "upload/Drums.mp3",
"volume" : "0.4",
"pan" : "-0.75",
"name" : "Drums"
}
],
"users" : ["adam", "eve"]
}
if I pass to Mongoose an object and try to use it to update like this:
var id = req.body._id;
var editedProject = req.body;
delete editedProject._id;
console.log("TRACKS: " +JSON.stringify(editedProject.tracks));
Project.update({"_id": id}, editedProject, {"upsert": true}, function(err, proj) {
if (err) {
res.send(err);
}
res.json(proj);
});
Only the 1st level properties are updated, but not the array.
I've found some solutions that involve looping through the array elements and calling an update for each element, but I would like to avoid that and keep the update as a single operation.
Or is it better to avoid using arrays?

How to query a single embedded document in an array in MongoDB?

I am trying to query a single embedded document in an array in MongoDB. I don't know what I am doing wrong. Programmatically, I will query this document and insert new embedded documents into the currently empty trips arrays.
{
"_id" : ObjectId("564b3300953d9d51429163c3"),
"agency_key" : "DDOT",
"routes" : [
{
"route_id" : "6165",
"route_type" : "3",
"trips" : [ ]
},
{
"route_id" : "6170",
"route_type" : "3",
"trips" : [ ]
},
...
]
}
Following queries -I run in mongo shell- return empty:
db.tm_routes.find( { routes : {$elemMatch: { route_id:6165 } } } ).pretty();
db.tm_routes.find( { routes : {$elemMatch: { route_id:6165,route_type:3 } } } ).pretty();
db.tm_routes.find({'routes.route_id':6165}).pretty()
also db.tm_routes.find({'routes.route_id':6165}).count() is 0.
The following query returns every document in the array
db.tm_routes.find({'routes.route_id':'6165'}).pretty();
{
"_id" : ObjectId("564b3300953d9d51429163c3"),
"agency_key" : "DDOT",
"routes" : [
{
"route_id" : "6165",
"route_type" : "3",
"trips" : [ ]
},
{
"route_id" : "6170",
"route_type" : "3",
"trips" : [ ]
},
...
]}
but db.tm_routes.find({'routes.route_id':'6165'}).count() returns 1.
And finally, here is how I inserted data in the first place -in Node.JS-:
async.waterfall([
...
//RETRIEVE ALL ROUTEIDS FOR EVERY AGENCY
function(agencyKeys, callback) {
var routeIds = [];
var routesArr = [];
var routes = db.collection('routes');
//CALL GETROUTES FUNCTION FOR EVERY AGENCY
async.map(agencyKeys, getRoutes, function(err, results){
if (err) throw err;
else {
callback(null, results);
}
});
//GET ROUTE IDS
function getRoutes(agencyKey, callback){
var cursor = routes.find({agency_key:agencyKey});
cursor.toArray(function(err, docs){
if(err) throw err;
for(i in docs){
routeIds.push(docs[i].route_id);
var routeObj = {
route_id:docs[i].route_id,
route_type:docs[i].route_type,
trips:[]
};
routesArr.push(routeObj);
/* I TRIED 3 DIFFERENT WAYS TO PUSH DATA
//1->
collection.update({agency_key:agencyKey}, {$push:{"routes":{
'route_id':docs[i].route_id,
'route_type':docs[i].route_type,
'trips':[]
}}});
//2->
collection.update({agency_key:agencyKey}, {$push:{"routes":routeObj}});
*/
}
// 3->
collection.update({agency_key:agencyKey}, {$push:{routes:{$each:routesArr}}});
callback(null, routeIds);
});
};
},
...
var collection = newCollection(db, 'tm_routes',[]);
function newCollection(db, name, options){
var collection = db.collection(name);
if (collection){
collection.drop();
}
db.createCollection(name, options);
return db.collection(name);
}
Note: I am not using Mongoose and don't want to use if possible.
Melis,
I see what you are asking for, and what you need is help understanding how things are stored in mongodb. Things to understand:
A document is the basic unit of data for MongoDB and can be roughly compared to a row in a relational database.
A collection can be thought of as a table with a dynamic schema
So documents are stored in collections.Every document has a special _id, that is unique within a collection. What you showed us above in the following format is One document.
{
"_id" : ObjectId("564b3300953d9d51429163c3"),
"agency_key" : "DDOT",
"routes" : [
{
"route_id" : "6165",
"route_type" : "3",
"trips" : [ ]
},
{
"route_id" : "6170",
"route_type" : "3",
"trips" : [ ]
},
...
]}
If you run a query in your tm_routes collection. The find() will return each document in the collection that matches that query. Therefore when you run the query db.tm_routes.find({'routes.route_id':'6165'}).pretty(); it is returning the entire document that matches the query. Therefore this statement is wrong:
The following query returns every document in the array
If you need to find a specific route in that document, and only return that route, depending on your use, because its an array, you may have to use the $-Positional Operator or the aggregation framework.
For Node and Mongodb users using Mongoose, this is one of the ways to write the query to the above problem:
db.tm_routes.updateOne(
{
routes: {
$elemMatch: {
route_id: 6165 (or if its in a route path then **6165** could be replaced by **req.params.routeid**
}
}
},
{
$push: {
"routes.$.trips":{
//the content you want to push into the trips array goes here
}
}
}
)

Mapreduce split input string into output array

I am dealing with documents like the following one:
> db.productData.find({"upc" : "XXX"}).pretty()
{
"_id" : ObjectId("538dfa3d44e19b2bcf590a77"),
"upc" : "XXX",
"productDescription" : "bla foo bar bla bla fooX barY",
"productSize" : "",
"ingredients" : "foo; bar; foo1; bar1.",
"notes" : "bla bla bla"
}
>
I would like to have a document containing, among the fields, a list/array of splitted ingredients (on the ;). I want to split the string of the original collection into an array of strings.
I would like to map only some of the input fields in the output collection.
I would like to use mapreduce on MongoDB.
I've tried many different ways moving stuff from the map function to the reduce function failing to find a proper solution.
From all the attempts I performed, now I know I need to check for null values etc, so the following one is my last attempt:
The map function:
var mapperProductData = function () {
var ingredientsSplitted = values.ingredientsString.split(';');
var objToEmit = {barcode : "", description : "", ingredients : []};
// checking for null (is this strictly necessary? why?)
if (
this.hasOwnProperty('ingredients')
&& this.hasOwnProperty('productDescription')
&& this.hasOwnProperty('upc')
) {
for (var i = 0; i < ingredientsSplitted.length; i++) {
// I want to emit a new document only when I have all the splitted strings inside the array
if (i == ingredientsSplitted.length - 1) {
objToEmit.barcode = this.upc;
objToEmit.description = this.productDescription;
objToEmit.ingredients = ingredientsSplitted;
emit(this.upc, objToEmit);
}
}
}
};
The reduce function:
var reducerNewMongoCollection = function(key, values) {
return values;
};
The map-reduce call:
db.productData.mapReduce(
mapperProductData,
reducerNewMongoCollection,
{
out : "newMongoCollection" ,
query: { "values" : {$exists: true} }
}
);
I am getting an empty collection in output (newMongoCollection is empty).
What am I doing wrong?
Let's start from the beginning. Your map function should look like this:
var mapperProductData = function () {
var ingredientsSplitted = this.ingredients.split(';');
var objToEmit = {
barcode : this.upc,
description : this.productDescription,
ingredients : ingredientsSplitted
};
emit(this.upc, objToEmit);
};
Your map-reduce call should be:
db.productData.mapReduce(
mapperProductData,
reducerNewMongoCollection,
{
out : "newMongoCollection",
query : {
upc : { $exists : true },
productDescription : { $exists : true },
ingredients : { $exists : true , $type : 4 }
}
}
);
The query part will filter the documents that do have relevant fields. Also the query parameter $type will match only documents where ingredients is an array. This way you don't need to do complicated checking inside your map function and the number of documents sent to map function will be lower.
The result for your test document document will look like this:
key : XXX,
value: {
"barcode" : "XXX",
"description" : "bla foo bar bla bla fooX barY",
"ingredients" : [
"foo",
" bar",
" foo1",
" bar1."
]
}

How can i find object from an array in mongoose

I have a schema like following : -
var P = {
s : [{
data : [],
sec : mongoose.Schema.Types.ObjectId
}]
};
Now I want to find only the object of section not entire the row. Like If I pass sec value I want only the value of s.data of that sec object.
example : -
{ s : [
{
data : [],
sec : '52b9830cadaa9d273200000d'
},{
data : [],
sec : '52b9830cadaa9d2732000005'
}
]
}
Result should be look like -
{
data : [],
sec : '52b9830cadaa9d2732000005'
}
I do not want all entire row. Is it possible? If yes, then please help me.
You can use the $elemMatch projection operator to limit an array field like s to a single, matched element, but you can't remove the s level of your document using find.
db.test.find({}, {_id: 0, s: {$elemMatch: {sec: '52b9830cadaa9d2732000005'}}})
outputs:
{
"s": [
{
"data": [ ],
"sec": "52b9830cadaa9d2732000005"
}
]
}
You can always get the value of some field by using find(). For example in your case:
db.collectionName.find({},{s.data:1})
So the first bracket is to apply any condition or query, and in the second bracket you have to define the field as 1(to fetch only those fields value).
Please check http://docs.mongodb.org/manual/reference/method/db.collection.find for more information.
Let me know if it solves your problem.
Not into Mongo or db but working with Pure JavaSript skills here is the Solution as you mentioned Node.js which would do the execution task of the below.
Schema
var P = { s : [
{
data : [],
sec : '52b9830cadaa9d273200000d'
},{
data : [],
sec : '52b9830cadaa9d2732000005'
}
]
};
Search Method Code
var search = function (search_sec){
for (var i=0; i<(P.s.length);i++){
var pointer = P.s[i].sec;
var dataRow = P.s[i];
if((pointer) === search_sec ){
console.log(dataRow);
}
}
};
Here is How you can call - search('search_id');
For example input :
search('52b9830cadaa9d2732000005');
Output:
[object Object] {
data: [],
sec: "52b9830cadaa9d2732000005"
}
Working Demo here - http://jsbin.com/UcobuVOf/1/watch?js,console

Resources