MongoDB - Update field with reference of another document - mongodb

I would like if is possible to update a field of all documents in a collection with a reference to another document. I have tried to do this with the code below:
var project = db.Project.find({slug:"engine"});
db.Activity.update({}, {$set:{'project':DBRef("Project", project._id, "mydb")}});
When I look at the Activity documents, in the "project" field, the result is:
{
_id: ObjectId("..."),
"project": DBRef("Project", undefined, "mydb")
}
Is there a way to do this correctly?
Thanks in advance.

Seems to me you're having a promise callback problem. You can solve it in two ways:
Option one: Put the function depending of your data return inside a callback of the first function, for example:
db.Project.find({slug:"engine"}, function(error, data) {
db.activity.update(...data.Id...);
});
Option two: Wait for the return of the find to be completed:
var project = db.Project.find({slug:"engine"});
project.then(function(error,data) {
db.activity.update(...project.Id...);
});
Both should work. The problem is that when you make the first call, it returns a promise, not the value itself. If you are making confusion on this topic, you can take a look at:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
Hope my answer helped you.

Related

Mongoose - populate return _id only instead of a Object [duplicate]

In Mongoose, I can use a query populate to populate additional fields after a query. I can also populate multiple paths, such as
Person.find({})
.populate('books movie', 'title pages director')
.exec()
However, this would generate a lookup on book gathering the fields for title, pages and director - and also a lookup on movie gathering the fields for title, pages and director as well. What I want is to get title and pages from books only, and director from movie. I could do something like this:
Person.find({})
.populate('books', 'title pages')
.populate('movie', 'director')
.exec()
which gives me the expected result and queries.
But is there any way to have the behavior of the second snippet using a similar "single line" syntax like the first snippet? The reason for that, is that I want to programmatically determine the arguments for the populate function and feed it in. I cannot do that for multiple populate calls.
After looking into the sourcecode of mongoose, I solved this with:
var populateQuery = [{path:'books', select:'title pages'}, {path:'movie', select:'director'}];
Person.find({})
.populate(populateQuery)
.execPopulate()
you can also do something like below:
{path:'user',select:['key1','key2']}
You achieve that by simply passing object or array of objects to populate() method.
const query = [
{
path:'books',
select:'title pages'
},
{
path:'movie',
select:'director'
}
];
const result = await Person.find().populate(query).lean();
Consider that lean() method is optional, it just returns raw json rather than mongoose object and makes code execution a little bit faster! Don't forget to make your function (callback) async!
This is how it's done based on the Mongoose JS documentation http://mongoosejs.com/docs/populate.html
Let's say you have a BookCollection schema which contains users and books
In order to perform a query and get all the BookCollections with its related users and books you would do this
models.BookCollection
.find({})
.populate('user')
.populate('books')
.lean()
.exec(function (err, bookcollection) {
if (err) return console.error(err);
try {
mongoose.connection.close();
res.render('viewbookcollection', { content: bookcollection});
} catch (e) {
console.log("errror getting bookcollection"+e);
}
//Your Schema must include path
let createdData =Person.create(dataYouWant)
await createdData.populate([{path:'books', select:'title pages'},{path:'movie', select:'director'}])

Meteor observeChanges(). How to check the actual changes?

I have a code that looks like this
Trades.find().observeChanges({
changed: function(id, fields) {
// do stuff
}
});
Where each Trades has an array of items inside
TradesSchema = new SimpleSchema({
// ...
items: {
type: [String]
},
// ...
});
Trades.attachSchema(TradesSchema);
These items are being changed sometimes, and I want to track the changes. The code works fine, except that in fields it returns all the items, not only the items that were changed.
Is there any way to see which exactly item was changed without changing the structure of the collection?
Thanks #Season for the hint!
observeChanges only gives the new values, so you have to use observe, since it returns both the new and old documents. Then you need to compare them to see what exactly got changed. (See docs for observe on meteor.com)
Trades.find().observe({
changed: function(newDocument, oldDocument) {
// compare newDocument to oldDocument to find out what has changed
}
});

Query sailsjs blueprint endpoints by id array using request

I'm using the request library to make calls from one sails app to another one which exposes the default blueprint endpoints. It works fine when I query by non-id fields, but I need to run some queries by passing id arrays. The problem is that the moment you provide an id, only the first id is considered, effectively not allowing this kind of query.
Is there a way to get around this? I could switch over to another attribute if all else fails but I need to know if there is a proper way around this.
Here's how I'm querying:
var idArr = [];//array of ids
var queryParams = { id: idArr };
var options: {
//headers, method and url here
json: queryParams
};
request(options, function(err, response, body){
if (err) return next(err);
return next(null, body);
});
Thanks in advance.
Sails blueprint APIs allow you to use the same waterline query langauge that you would otherwise use in code.
You can directly pass the array of id's in the get call to receive the objects as follows
GET /city?where={"id":[1, 2]}
Refer here for more.
Have fun!
Alright, I switched to a hacky solution to get moving.
For all models that needed querying by id arrays, I added a secondary attribute to the model. Let's call it code. Then, in afterCreate(), I updated code and set it equal to the id. This incurs an additional database call, but it's fine since it's called just once - when the object is created.
Here's the code.
module.exports = {
attributes: {
code: {
type: 'string'//the secondary attribute
},
// other attributes
},
afterCreate: function (newObj, next) {
Model.update({ id: newObj.id }, { code: newObj.id }, next);
}
}
Note that newObj isn't a Model object as even I was led to believe. So we cannot simply update its code and call newObj.save().
After this, in the queries having id arrays, substituting id with code makes them work as expected!

How to call cascading find() in Meteor + MongoDB?

I use Meteor & Iron-router. I have the following data context defined in the router:
data: function() { return {
eventId: this.params._id,
registrants: Registrants.find({eventIds: {$elemMatch: { $in: [this.params._id]}}}, {sort: {name:1, phone:1, email:1}}),
}}
I want to enable Registrants to be filtered further by user input. In my case, I already have ReactiveVar called filterName which listen to input text from user. Whenever the input text changed, the filterName is updated. ( I followed this answer ng-repeat + filter like feature in Meteor Blaze/Spacebars)
Now, I want to add $and to the Registrants.find() method to derive new registrants data context. How should I do it so that the query is reactive to the filterName?
Another approach is by defining Template helper method filteredRegistrants. Initially, its value is the same as return this.registrants. Whenever filterName changed, I would do return this.registrants.find({name: filterName}), but somehow I can't invoke find from registrants cursor, can I? I got undefined is not function error when doing that.
this.registrants is already a cursor (result of Registrants.find()), and not a collection, thus it doesn't have the find() method you look for. However, there is nothing wrong with making another query in the helper if the functionality provided by your controller is not enough:
Template.registrantsTemplate.helpers({
filteredRegistrants: function() {
return Registrants.find(...query...);
},
});

How to update a doc value from an iron-router function

I know this is a nood question, but I'm trying to work out how to update a value in a document from a route in iron router. I've found the spot I need to put the function, but I'm struggling with the mongo code needed to make it work.
I'm trying to increment a views element each time a link is clicked, so have added the following code to the route.
data: function () {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
console.log(project.views);
projectDocs.update({id: project.id},
{$inc: {views: 1}}
);
console.log(project.views);
return project;
}
});
The project.views value is returning the correct value, but the code to update the value throws an exception at the moment.
I tried the simple thing of project.views++ which increments the variable within the function but it never gets pushed to the database (no surprises there I guess).
Can someone point me in the direction I need to get this value to inc (and is this even the right place to do this?).
Thanks.
Peter.
OK, I found this link that has lead me part of the way http://books.google.com.au/books?id=uGUKiNkKRJ0C&pg=PA37&lpg=PA37&dq=Cannot+apply+$inc+modifier+to+non-number&source=bl&ots=h7qyOddRsf&sig=EWFw9kNLGHoFEUS-nTNsBStDRcQ&hl=en&sa=X&ei=cRGXUse0DNGciAfk6YHgCA&ved=0CFcQ6AEwBQ#v=onepage&q=Cannot%20apply%20%24inc%20modifier%20to%20non-number&f=false which explains that you can only inc numeric values (I had this as a string it seems.
Now the problem is that I seem to be in an endless loop.
The function now looks like
this.route('projectPage', {
path: '/projects/:_id',
waitOn: function() {
return Meteor.subscribe('singleProject', this.params._id);
},
data: function () {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
console.log("Views", project.views);
console.log("Project", project);
projectDocs.update(project._id,
{$inc: {views: 1}}
);
console.log(project.views);
return project;
}
});
Why would this be looping?
Use _id instead of id. So
projectDocs.update({_id: project._id},
{$inc: {views: 1}}
);
If that's not it, perhaps you could update your answer with whatever exception you are getting.
Just read the fantastic new documentation on iron-router a bit further and moved the $inc function to the unload hook and all seems to be good.
this.route('projectPage', {
path: '/projects/:_id',
waitOn: function() {
return Meteor.subscribe('singleProject', this.params._id);
},
data: function () {
return projectDocs.findOne(this.params._id);
},
unload: function() {
var project = projectDocs.findOne(this.params._id);
// need to increment views value by one
projectDocs.update(project._id,
{$inc: {views: 1}}
);
}
// could possibly use layout: popup_layout? here
});
Would love some confirmation that this is actually where I should be doing this (and it does seem a bit inefficient to be doing so many "findOne"'s) but its working for the moment.