Does Moongoose 3.8.8 support $position operator? - mongodb

Does Moongoose 3.8.8 (the lastest version) support $position (http://docs.mongodb.org/manual/reference/operator/update/position/) operator from MongoDB 2.6.0?
In the following code example the new elements is inserted in the end of the array userActivity.activities:
model:
var userActivity = new schema({
userId: {type:String, required:true, unique:true},
activities: [activity]
});
var activity = new schema({
act: {type: Number, required:true},
});
query:
var activity = { act: 1 };
model.userActivity.update(
{ _id: dbact._id },
{ $push: { activities: {
$each: [ activity ],
$position: 0
}
}
},
function (err, numAffected) {
if (!err) {
// do something
}
});

This actually doesn't matter and never matters for any "framework" implementation and I do not mind explaining why.
Every single "framework" ( such as Mongoose, Mongoid, Doctrine, MongoEngine, etc, etc, etc ) are all basically built upon a basic "driver" implementation that has in most cases been developedby the MongoDB staff themselves. So the basic functionality is always ther even if you need to "delve" down to a level in order to use those "native" methods.
So here would be the native usage example in this case:
List.collection.update(
{},
{ "$push": {
"list": {
"$each": [ 1, 2, 3 ],
"$position": 0 }
}
},function(err,NumAffected) {
console.log("done");
});
Note the "collection" method used from the model, which is getting the "raw" collection details from the driver. So you are using it's method and not some "wrapped" method that may be doing additional processing.
The next and most basic reason is if you cannot find the method and application of the operators that you need the here is a simple fact.
Every single operation as used by the methods in every framework and basic driver method is essentially a call to the "runCommand" method in the basic API. So since that basic call is available everywhere ( in some form or another, because it has to be ), then you can do everything that you find advertised on the MongoDB site with every language implementation on any framework.
But the short call to your particular request is, since this is not actually a method call but is simply part of the BSON arguments as passed in, then of course there is no restriction by a particular language driver to actually use this.
So you can use these new argument without of course updating to the most recent version. But you probably will get some nice methods to do so if you actually do.

Yes, you should be able to use it directly as Mongoose will pass through the update clause:
Model.update(
query, /* match the document */
{ $push:
{ yourArrayField:
{
$each: [ 1, 2, 3 ],
$position: 0
}
}
}, function (err, res) { /* callback */ });
The above would insert the values 1, 2, 3 at the front of the array named yourArrayField.
As it's just a pass-through, you'll need to make sure it works with the server version that you're connecting the client to.

Related

MongoDB View vs Function to abstract query and variable/parameter passed

I hate to risk asking a duplicate question, but perhaps this is different from Passing Variables to a MongoDB View which didn't have any clear solution.
Below is a query to find the country for IP Address 16778237. (Outside the scope of this query, there is a formula that turns an IPV4 address into a number.)
I was wondering if we could abstract away this query out of NodeJS code, and make a view, so the view could be called from NodeJS. But the fields ipFrom and ipTo are indexed to get the query to run fast against millions of documents in the collection, so we can't return all the rows to NodeJS and filter there.
In MSSQL maybe this would have to be a stored procedure, instead of a view. Just trying to learn what is possible in MongoDB. I know there are functions, which are written in JavaScript. Is that where I need to look?
db['ip2Locations'].aggregate(
{
$match:
{
$and: [
{
"ipFrom": {
$lte: 16778237
}
},
{
"ipTo": {
$gte: 16778237
}
},
{
"active": true
}
],
$comment: "where 16778237 between startIPRange and stopIPRange and the row is 'Active',sort by createdDateTime, limit to the top 1 row, and return the country"
}
},
{
$sort:
{
'createdDateTime': - 1
}
},
{
$project:
{
'countryCode': 1
}
},
{
$limit: 1
}
)
Part 2 - after more research and experimenting, I found this is possible and runs with success, but then see trying to make a view below this query.
var ipaddr = 16778237
db['ip2Locations'].aggregate(
{
$match:
{
$and: [
{
"ipFrom": {
$lte: ipaddr
}
},
{
"ipTo": {
$gte: ipaddr
}
},
{
"active": true
}
],
$comment: "where 16778237 between startIPRange and stopIPRange and the row is 'Active',sort by createdDateTime, limit to the top 1 row, and return the country"
}
},
{
$sort:
{
'createdDateTime': - 1
}
},
{
$project:
{
'countryCode': 1
}
},
{
$limit: 1
}
)
If I try to create a view with a "var" in it, like this;
db.createView("ip2Locations_vw-lookupcountryfromip","ip2Locations",[
var ipaddr = 16778237
db['ip2Locations'].aggregate(
I get error:
[Error] SyntaxError: expected expression, got keyword 'var'
In the link I provided above, I think the guy was trying to figure how the $$user-variables work (no example here: https://docs.mongodb.com/manual/reference/aggregation-variables/). That page refers to $let, but never shows how the two work together. I found one example here: https://www.tutorialspoint.com/mongodb-query-to-set-user-defined-variable-into-query on variables, but not $$variables. I'm
db.createView("ip2Locations_vw-lookupcountryfromip","ip2Locations",[
db['ip2Locations'].aggregate(
...etc...
"ipFrom": {
$lte: $$ipaddr
}
I tried ipaddr, $ipaddr, and $$ipaddr, and they all give a variation of this error:
[Error] ReferenceError: $ipaddr is not defined
In a perfect world, one would be able to do something like:
get['ip2Locations_vw-lookupcountryfromip'].find({$let: {'ipaddr': 16778237})
or similar.
I'm getting that it's possible with Javascript stored in MongoDB (How to use variables in MongoDB query?), but I'll have to re-read that; seems like some blogs were warning against it.
I have yet to find a working example using $$user-variables, still looking.
Interpretation
You want to query a view from some server side code, passing a variable to it.
Context
Can we use an external variable to recompute a View? Take the following pipeline:
var pipeline = [{ $group:{ _id:null, useless:{ $push:"$$NOW" } } }]
We can pass system variables using $$. We can define user variables too, but the user defined variables are made out of:
Collection Data
System Variables.
Also, respect to your Part2:
A variable var variable="what" will be computed only once. Redefine variable="whatever" makes no difference in the view, it uses "what".
Conclusion
Views can only be re-computed with system variables, or user variables dependant on those system variables or collection data.
Added an answer to the post you link too.

Updating a value that is dependent on a newly updated document key

My goal is to add a comment to my CommentFeed and while doing that I want to push that comment into my topComments field and also update the 'numOfComments' . I want to limit the topComments to only 3 comments (How would I even set that up?). And how do I take the previous value of numOfComments and add one to it?
CommentFeed.findOneAndUpdate(
{ _id: commentId },
{
$push: {
comments: {
text: req.body.text
},
$push: topComments:{text: req.body.text}, <--- Limit this somehow to only allow an array length of 3?
$set: numOfComments: ? , <---What kind of logic is used here?
}
},
{ new: true }
)
CommentFeed Schema
const CommentFeedSchema = new Schema({
topComments:[{text:{type:String}}],
numOfComments:{type:Number},
comments: [
text: { type: String, required: true }
]});
For the first issue (limiting the topComments array size) you can use the $slice operator. This has already been answered in other questions. But you might consider computing topComments from comments using the$slice operator in the projection argument:
CommentFeed.find( {}, { comments: { $slice: -3 } } )
For the second issue (updating a document using existing fields from that document), it is not something you can do in a simple findOneAndUpdate call. This was also discussed in other questions.
But you might consider computing numOfComments instead of updating it every time. You can do that with the $size operator of the aggregation framework:
CommentFeed.aggregate({$project: { numOfComments: { $size:"$comments" }}})

Can I use Sails native query with populate?

I am using the native method of sails-mongo to query a collection. I need to use native to access some of the Mongo geospatial query features.
I would like to use the sails populate syntax to include associated models.
Is there a way to do this?
Here is an example of my existing code:
Trip.native(function(err, collection) {
collection.find(
{
"locationTo": {
"$near": {
"$maxDistance": 80467.35439432222,
"$geometry": {
"type": "Point",
"coordinates": [-117.133655, 32.720519]
}
}
}
}
)
.toArray(function(err, trips) {
console.log("Trips nearby:", trips);
});
});
Here is my Trip model for reference.
var Trip = {
attributes: {
owner: {
model: 'User'
},
title: 'string',
addressFrom: 'string',
locationFrom: 'json', // geoJson
dateTimeDepart: 'datetime',
dateTimeArrive: 'datetime',
dateTimeReturn: 'datetime',
addressTo: 'string',
locationTo: 'json', // geoJson
driver: {
model: 'User'
},
status: {
type: 'string',
defaultsTo: 'PENDING'
}
}
}
Would be helpful if you share the Trip model as well. If the field you wish to populate has type "collection" (not "array"), you should be able to populate it just fine.
Update: Alright, I got your question wrong. There doesn't seem to be any way of populating directly after a native call. There's really not much you can do with a native call as far as Waterline functions are concerned. I would suggest either running another query(Waterline) after you've fetched locationTo or populating the fields yourself since you only need to populate two of them(and that too from the same model). I can't think of anything that would suffice with a single query.
Thanks, I ended up doing it in two queries for now.
First, I build an array of matching ID's via the native query.
var tripIdList = trips.map(function (trip) {
return trip._id
});
Second, I do a normal find query using the ID list. It's not a single query but works well. Thanks for the help
Trip.find(filter)
.where({id: tripIdList})
.populate('driver')
.exec(function (err, trips) {
console.log("Trips:", trips);
}

How do I use new Meteor.Collection.ObjectID() in my mongo queries with meteor?

I have a Collection that has documents with an array of nested objects.
Here is fixture code to populate the database:
if (Parents.find().count() == 0) {
var parentId = Parents.insert({
name: "Parent One"
});
Children.insert({
parent: parentId,
fields: [
{
_id: new Meteor.Collection.ObjectID(),
position: 3,
name: "three"
},
{
_id: new Meteor.Collection.ObjectID(),
position: 1,
name: "one"
},
{
_id: new Meteor.Collection.ObjectID(),
position: 2,
name: "two"
},
]
});
}
You might be asking yourself, why do I even need an ObjectID when I can just update based off of the names. This is a simplified example to a much more complex schema that I'm currently working on and the the nested object are going to be created dynamically, the ObjectID's are definitely going to be necessary to make this work.
Basically, I need a way to save those nested objects with a unique ID and be able to update the fields by their _id.
Here is my Method, and the call I'm making from the browser console:
Meteor.methods({
upChild: function( options ) {
console.log(new Meteor.Collection.ObjectID());
Children.update({_id: options._id, "fields._id": options.fieldId }, {$set: {"fields.$.position": options.position}}, function(error){
if(error) {
console.log(error);
} else {
console.log("success");
}
});
}
});
My call from the console:
Meteor.call('upChild', {
_id: "5NuiSNQdNcZwau92M",
fieldId: "9b93aa1ef3868d762b84d2f2",
position: 1
});
And here is a screenshot of the html where I'm rendering all of the data for the Parents and Children collections:
Just an observation, as I was looking how generate unique IDs client side for a similar reason. I found calling new Meteor.Collection.ObjectID() was returning a object in the form 'ObjectID("abc...")'. By assigning Meteor.Collection.ObjectID()._str to _id, I got string as 'abc...' instead, which is what I wanted.
I hope this helps, and I'd be curious to know if anyone has a better way of handling this?
Jason
Avoid using the _str because it can change in the future. Use this:
new Meteor.Collection.ObjectID().toHexString() or
new Meteor.Collection.ObjectID().valueOf()
You can also use the official random package:
Random.id()

Average Aggregation Queries in Meteor

Ok, still in my toy app, I want to find out the average mileage on a group of car owners' odometers. This is pretty easy on the client but doesn't scale. Right? But on the server, I don't exactly see how to accomplish it.
Questions:
How do you implement something on the server then use it on the client?
How do you use the $avg aggregation function of mongo to leverage its optimized aggregation function?
Or alternatively to (2) how do you do a map/reduce on the server and make it available to the client?
The suggestion by #HubertOG was to use Meteor.call, which makes sense and I did this:
# Client side
Template.mileage.average_miles = ->
answer = null
Meteor.call "average_mileage", (error, result) ->
console.log "got average mileage result #{result}"
answer = result
console.log "but wait, answer = #{answer}"
answer
# Server side
Meteor.methods average_mileage: ->
console.log "server mileage called"
total = count = 0
r = Mileage.find({}).forEach (mileage) ->
total += mileage.mileage
count += 1
console.log "server about to return #{total / count}"
total / count
That would seem to work fine, but it doesn't because as near as I can tell Meteor.call is an asynchronous call and answer will always be a null return. Handling stuff on the server seems like a common enough use case that I must have just overlooked something. What would that be?
Thanks!
As of Meteor 0.6.5, the collection API doesn't support aggregation queries yet because there's no (straightforward) way to do live updates on them. However, you can still write them yourself, and make them available in a Meteor.publish, although the result will be static. In my opinion, doing it this way is still preferable because you can merge multiple aggregations and use the client-side collection API.
Meteor.publish("someAggregation", function (args) {
var sub = this;
// This works for Meteor 0.6.5
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
// Your arguments to Mongo's aggregation. Make these however you want.
var pipeline = [
{ $match: doSomethingWith(args) },
{ $group: {
_id: whatWeAreGroupingWith(args),
count: { $sum: 1 }
}}
];
db.collection("server_collection_name").aggregate(
pipeline,
// Need to wrap the callback so it gets called in a Fiber.
Meteor.bindEnvironment(
function(err, result) {
// Add each of the results to the subscription.
_.each(result, function(e) {
// Generate a random disposable id for aggregated documents
sub.added("client_collection_name", Random.id(), {
key: e._id.somethingOfInterest,
count: e.count
});
});
sub.ready();
},
function(error) {
Meteor._debug( "Error doing aggregation: " + error);
}
)
);
});
The above is an example grouping/count aggregation. Some things of note:
When you do this, you'll naturally be doing an aggregation on server_collection_name and pushing the results to a different collection called client_collection_name.
This subscription isn't going to be live, and will probably be updated whenever the arguments change, so we use a really simple loop that just pushes all the results out.
The results of the aggregation don't have Mongo ObjectIDs, so we generate some arbitrary ones of our own.
The callback to the aggregation needs to be wrapped in a Fiber. I use Meteor.bindEnvironment here but one can also use a Future for more low-level control.
If you start combining the results of publications like these, you'll need to carefully consider how the randomly generated ids impact the merge box. However, a straightforward implementation of this is just a standard database query, except it is more convenient to use with Meteor APIs client-side.
TL;DR version: Almost anytime you are pushing data out from the server, a publish is preferable to a method.
For more information about different ways to do aggregation, check out this post.
I did this with the 'aggregate' method. (ver 0.7.x)
if(Meteor.isServer){
Future = Npm.require('fibers/future');
Meteor.methods({
'aggregate' : function(param){
var fut = new Future();
MongoInternals.defaultRemoteCollectionDriver().mongo._getCollection(param.collection).aggregate(param.pipe,function(err, result){
fut.return(result);
});
return fut.wait();
}
,'test':function(param){
var _param = {
pipe : [
{ $unwind:'$data' },
{ $match:{
'data.y':"2031",
'data.m':'01',
'data.d':'01'
}},
{ $project : {
'_id':0
,'project_id' : "$project_id"
,'idx' : "$data.idx"
,'y' : '$data.y'
,'m' : '$data.m'
,'d' : '$data.d'
}}
],
collection:"yourCollection"
}
Meteor.call('aggregate',_param);
}
});
}
If you want reactivity, use Meteor.publish instead of Meteor.call. There's an example in the docs where they publish the number of messages in a given room (just above the documentation for this.userId), you should be able to do something similar.
You can use Meteor.methods for that.
// server
Meteor.methods({
average: function() {
...
return something;
},
});
// client
var _avg = { /* Create an object to store value and dependency */
dep: new Deps.Dependency();
};
Template.mileage.rendered = function() {
_avg.init = true;
};
Template.mileage.averageMiles = function() {
_avg.dep.depend(); /* Make the function rerun when _avg.dep is touched */
if(_avg.init) { /* Fetch the value from the server if not yet done */
_avg.init = false;
Meteor.call('average', function(error, result) {
_avg.val = result;
_avg.dep.changed(); /* Rerun the helper */
});
}
return _avg.val;
});