Mongoose/MongoDb getting error geoNear is not a function - node.js

This is my controller file locations.js
var mongoose = require('mongoose');
var Loc = mongoose.model('location');
module.exports.locationsListByDistance = function(req, res) {
var lng = parseFloat(req.query.lng);
var lat = parseFloat(req.query.lat);
var point = {
type: "Point",
coordinates: [lng, lat]
};
var geoOptions = {
spherical: true,
maxDistance: 1000
};
Loc.geoNear(point, geoOptions, function (err, results, stats) {
console.log(results);
});
};
My model file locations.js
var mongoose = require('mongoose');
var reviewSchema = new mongoose.Schema({
author: String,
rating: {
type: Number,
required: true,
min: 0,
max: 5
},
reviewText: String,
createdOn: {
type: Date,
"default": Date.now
}
});
var openingTimeSchema = new mongoose.Schema({
days: {
type: String,
required: true
},
opening: String,
closing: String,
closed: {
type: Boolean,
required: true
}
});
var locationSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
address: String,
rating: {
type: Number,
"default": 0,
min: 0,
max: 5
},
facilities: [String],
// Always store coordinates longitude, latitude order.
coords: {
type: [Number],
index: '2dsphere'
},
openingTimes: [openingTimeSchema],
reviews: [reviewSchema]
});
mongoose.model('location', locationSchema, 'locations');
Whenever I run http://localhost:3000/api/locations?lng=-0.9690884&lat=51.455041 I get error geoNear is not a function
TypeError: Loc.geoNear is not a function
at module.exports.locationsListByDistance (/home/shackers/Projects/mean/loc8r/app_api/controllers/locations.js:51:7)
at Layer.handle [as handle_request] (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/layer.js:95:5)
at next (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/route.js:137:13)
at Route.dispatch (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/route.js:112:3)
at Layer.handle [as handle_request] (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/layer.js:95:5)
at /home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:281:22
at Function.process_params (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:335:12)
at next (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:275:10)
at Function.handle (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:174:3)
at router (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:47:12)
at Layer.handle [as handle_request] (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/layer.js:95:5)
at trim_prefix (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:317:13)
at /home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:284:7
at Function.process_params (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:335:12)
at next (/home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:275:10)
at /home/shackers/Projects/mean/loc8r/node_modules/express/lib/router/index.js:635:15
This are versions of dependencies i am using:
node : 8.9.3 npm : 5.5.1 express : 4.15.5 mongoose : 5.0.0
mongoDb : 3.6.1

router.get('/', () => {
Loc.aggregate([
{
$geoNear: {
near: 'Point',
distanceField: "dist.calculated",
maxDistance: 100000,
spherical: true
}
}
]).then(function(err, results, next){
res.send();
}).catch(next);
});
Ref:- https://docs.mongodb.com/manual/reference/command/geoNear/

This error is happening because .geoNear used to be supported, but is no longer supported as of Mongoose 5, which uses the Node MongoDB v3 driver.
The issue is documented in the Migrating to Mongoose 5 document, which in turn links to the MongoDB 3 drive release notes which provides this statement about recommend replacements:
The functionality of the geoNear command is duplicated elsewhere in the language, in the $near/$nearSphere query operators on unsharded collections, and in the $geoNear aggregation stage on all collections.
Effectively, the official docs are endorsing kind of use of $geoNear documented in other answers.

I'm having the exact same problem and I've abandoned the approach I was using before (which looks like you were having too). The following is an alternative that does not throw an error and should give you the same result you were after using Loc.geoNear:
Loc.aggregate(
[
{
'$geoNear': {
'near': point,
'spherical': true,
'distanceField': 'dist',
'maxDistance': 1000
}
}
],
function(err, results) {
// do what you want with the results here
}
)

Apparently I'm in the same book (Getting Mean, Manning) and running into roughly the same issues. This seems to work for me:
var mongoose = require('mongoose');
var Loc = mongoose.model('Location');
var sendJSONresponse = function(res, status, content) {
res.status(status);
res.json(content);
};
var theEarth = (function() {
console.log('theEarth');
var earthRadius = 6371; // km, miles is 3959
var getDistanceFromRads = function(rads) {
return parseFloat(rads * earthRadius);
};
var getRadsFromDistance = function(distance) {
return parseFloat(distance / earthRadius);
};
return {
getDistanceFromRads: getDistanceFromRads,
getRadsFromDistance: getRadsFromDistance
};
})();
/* GET list of locations */
module.exports.locationsListByDistance = function(req, res) {
console.log('locationsListByDistance:');
var lng = parseFloat(req.query.lng);
var lat = parseFloat(req.query.lat);
var maxDistance = parseFloat(req.query.maxDistance);
var point = {
type: "Point",
coordinates: [lng, lat]
};
console.log('point: ' + point)
var geoOptions = {
spherical: true,
maxDistance: theEarth.getRadsFromDistance(maxDistance),
num: 10
};
console.log('geoOptions: ' + geoOptions);
if ((!lng && lng!==0) || (!lat && lat!==0) || ! maxDistance) {
console.log('locationsListByDistance missing params');
sendJSONresponse(res, 404, {
"message": "lng, lat and maxDistance query parameters are all required"
});
return;
} else {
console.log('locationsListByDistance running...');
Loc.aggregate(
[{
'$geoNear': {
'near': point,
'spherical': true,
'distanceField': 'dist.calculated',
'maxDistance': maxDistance
}
}],
function(err, results) {
if (err) {
sendJSONresponse(res, 404, err);
} else {
locations = buildLocationList(req, res, results);
sendJSONresponse(res, 200, locations);
}
}
)
};
};
var buildLocationList = function(req, res, results) {
console.log('buildLocationList:');
var locations = [];
results.forEach(function(doc) {
locations.push({
distance: doc.dist.calculated,
name: doc.name,
address: doc.address,
rating: doc.rating,
facilities: doc.facilities,
_id: doc._id
});
});
return locations;
};
returns a result list similar to such:
[
{
"distance": 0,
"name": "Rathaus",
"address": "Markt",
"rating": 0,
"facilities": [
"museum"
],
"_id": "5a9366517775811a449e503e"
},
{
"distance": 61.77676881925853,
"name": "Haus Löwenstein",
"address": "",
"rating": 0,
"facilities": [
"museum"
],
"_id": "5a9366517775811a449e5045"
},
{
"distance": 63.03445976427102,
"name": "Goldener Schwan",
"address": "Markt 37",
"rating": 0,
"facilities": [
"restaurant"
],
"_id": "5a9366517775811a449e5052"
},
{
"distance": 66.60375653163021,
"name": "Klein Printenbäckerei",
"address": "Krämerstraße 12",
"rating": 0,
"facilities": [
"supermarket"
],
"_id": "5a9366517775811a449e504d"
},
{
"distance": 74.91278395082011,
"name": "Couven-Museum",
"address": "Hühnermarkt 17",
"rating": 0,
"facilities": [
"museum"
],
"_id": "5a9366517775811a449e5042"
},
{
"distance": 132.2939512054143,
"name": "Cathedral Treasury",
"address": "Johannes-Paul-II.-Straße",
"rating": 0,
"facilities": [
"museum"
],
"_id": "5a9366517775811a449e503d"
},
{
"distance": 152.11867357742042,
"name": "Aachen Cathedral",
"address": "Domhof 1",
"rating": 0,
"facilities": [
"museum"
],
"_id": "5a9366517775811a449e503c"
},
{
"distance": 155.92015153163268,
"name": "International Newspaper Museum",
"address": "Pontstraße 13",
"rating": 0,
"facilities": [
"museum"
],
"_id": "5a9366517775811a449e5040"
},
{
"distance": 175.0857109968383,
"name": "Nobis Printen",
"address": "Münsterplatz 3",
"rating": 0,
"facilities": [
"supermarket"
],
"_id": "5a9366517775811a449e504c"
},
{
"distance": 179.32348875834543,
"name": "Grashaus",
"address": "Fischmarkt",
"rating": 0,
"facilities": [
"museum"
],
"_id": "5a9366517775811a449e5044"
},
{
"distance": 189.8675948747873,
"name": "Maranello",
"address": "Pontstraße 23",
"rating": 0,
"facilities": [
"restaurant"
],
"_id": "5a9366517775811a449e5057"
},
{
"distance": 198.2239741555585,
"name": "Carlos I",
"address": "Rennbahn 1",
"rating": 0,
"facilities": [
"restaurant"
],
"_id": "5a9366517775811a449e5055"
}
]
Not sure how accurate it is - got a list of addresses loaded and not 100% sure what's close to what in the random mess... but it returns a list and I'll test correctness somehow at some point.

I found the solution. Just downgrade mongoose and install version 4.9.1. Latest release of mongoose does not support Loc.geoNear
npm remove mongoose
npm install mongoose#4.9.1

More straightforward IMO, than the previous two answers in the Grider's course is:
index(req, res, next) {
const { lng, lat } = req.query;
Driver.find({
'geometry.coordinates': {
$nearSphere: {
$geometry: {
type: 'Point',
coordinates:[parseFloat(lng), parseFloat(lat)]
},
$maxDistance: 200000,
},
}
})
.then(drivers => res.send(drivers))
.catch(next);
}
This is in the spirit of the original definition he gives and uses the new functions which do the same thing as the old geoNear, except they've split out the spherical and non-spherical versions now. You'll need:
beforeEach((done) => {
const { drivers } = mongoose.connection.collections;
drivers.drop()
.then(() => drivers.createIndex({ 'geometry.coordinates': '2dsphere' }))
.then(() => done())
.catch(() => done());
};
In the test helper as mentioned before.

I think you're looking for this, please correct me if there is mistake there.
module.exports.locationsListBydistance = function (req, res) {
var lng = parseFloat(req.query.lng);
var lat = parseFloat(req.query.lat);
Loc.aggregate(
[{
$geoNear: {
'near': {'type':'Point', 'coordinates':[lng, lat]},
'spherical': true,
'maxdistance': theEarth.getRadsFromDistance(20),
'num':10,
'distanceField': 'dist'
}
}
], function(err, results) {
var locations = [];
console.log(results);
results.forEach(function (doc) {
locations.push({
distance: theEarth.getDistanceFromRads(doc.dist),
name: doc.name,
address: doc.address,
facilities: doc.facilities,
rating: doc.rating,
_id: doc._id
});
});
sendJsonResponse(res, 200, locations);
});
};

router.get('/',function(req,res,next){
Loc.aggregate([
{
$geoNear: {
near: {type:'Point', coordinates:[parseFloat(req.query.lng), parseFloat(req.query.lat)]},
distanceField: "dist.calculated",
maxDistance: 1000,
spherical: true
}
}
]).then(function(Locs){
res.send(Locs)
})
})

Model.geoNear() has been removed because the MongoDB driver no longer supports it

Model.geoNear() has been removed because the MongoDB driver no longer supports it, so you should use the aggregation method for example :
for mongoose v4.2:
my code was like this :
Modal.geoNear(
{type: 'Point', coordinates: [parseFloat(req.query.lng), parseFloat(req.query.lat)]},
{maxDistance: 100000, spherical: true}
)
.then(results => {
res.send(results);
})
for the latest mongoose version :
the code is :
Modal.aggregate([{
$geoNear: {
near: {
type: 'Point',
coordinates: [parseFloat(req.query.lng), parseFloat(req.query.lat)]
},
distanceField: 'distance',
maxDistance: 100000,
spherical: true
}
}])
.then(results => {
res.send(results);
})
Hope i answer or solve your problem, happy coding :D

The answer given by user : phao5814 is quite right I tried it out and must say It worked out well for me

index(req, res, next)
{
const { lng, lat } = req.query;
Driver.aggregate([
{
'$geoNear': {
"near": { 'type': 'Point',
'coordinates': [parseFloat(lng), parseFloat(lat)] },
"spherical": true,
"distanceField": 'dist',
"maxDistance": 200000
}
}
])
.then(drivers => res.send(drivers))
.catch(next);
}

Related

Mongoose aggregation With GraphQL returning error

I'm working on a project and currently using Mongodb Time-Series and aggregation.
I connected my Apollo Graphql to retrieve the data but i'm stuck with an error that i can not solve no matter what.
Float cannot represent non numeric value:
On my db the numbers are saved as Double. If i try to run the query in MongoDB Aggregation with MongoDBCompass it's working perfectly but not on my Nodejs database.
I tried googling my problem first and looking ad docs but all the different solution i found do not solve my problem, any suggestions?
This is my Query:
module.exports = async (root, { limit }, { models }) => {
const keyx = await models.Weather.aggregate([
{
$group: {
_id: {
yearMonthDay: {
$dateToString: { format: "%Y-%m-%d", date: "$timestamp" },
},
},
temp: {
$push: { temp: "$temp" },
},
},
},
])
.exec();
return keyx;
};
This is my model:
const mongoose = require("mongoose");
const { Schema } = mongoose;
mongoose.pluralize(null);
const weather = new Schema({
timestamp: {
type: String,
trim: true,
},
temp: {
type: Number,
trim: true,
},
});
const Weather = mongoose.model("time_weather", weather);
module.exports = { Weather };
and this is my types:
const { gql } = require("apollo-server");
module.exports = gql`
type Weather {
timestamp: String
_id: ID
temp: Float
}
type Query {
weather(limit: Int): [Weather]
}
`;
and finally this is the response i'm getting from apollo studio:
{
"errors": [
{
"message": "Float cannot represent non numeric value: [{ temp: 11.7 }, { temp: 11.7 }, { temp: 12.01 }, { temp: 13.21 }, { temp: 13.21 }, { temp: 11.93 }, { temp: 13.21 }, { temp: 12.73 }, { temp: 14.21 }, { temp: 11.7 }, ... 14 more items]",
"locations": [
{
"line": 6,
"column": 5
}
],
"path": [
"weather",
0,
"temp"
],
}
},

How to post and populate bill

I've got a relational json called "client" inside Bill's model. This is my code:
const mongoose = require("mongoose");
const { Schema } = mongoose;
const billSchema = new Schema({
number: Number,
date: { type: Date, default: Date.now() },
type: String,
local: String,
client: { type: mongoose.Schema.Types.ObjectId, ref: "clients", required: true },
detail: [
{
quantity: Number,
product: { code: Number, name: String, price: Number },
undertotal: Number
}
],
total: Number
});
mongoose.model("bills", billSchema);
this is my post route:
app.post("/api/bills", async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
await Client.findById(req.body.client._id).then(client => {
if (!client) {
return res.status(404).json({
message: "client not found"
});
}
});
const bill = new Bill({
number,
date: new Date(),
type,
local,
client,
detail,
total
});
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === "MongoError") {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
//my get route
app.get("/api/bills", function(req, res) {
Bill.find({}, function(err, bills) {
Client.populate(bills, { path: "clients" }, function(err, bills) {
res.status(200).send(bills);
});
});
});
I want something like this:
{
"number": 302,
"type": "c",
"local": "porstmouth",
"client": {
"address": {
"street": "victoria street",
"number": 1001,
"floor": "2",
"flat": 4
},
"_id": "5dab929613fb682b48e4ca6b",
"name": "luke skywalker",
"mail": "l.skywalker#yahoo.com",
"cuil": "39193219",
"phone": 128391,
"__v": 0
},
"detail": [
{
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000
}
But I see this result:
{
"date": "2019-10-20T12:27:17.162Z",
"_id": "5dac52a577e09b4acc45718d",
"number": 302,
"type": "c",
"local": "porstmouth ",
"client": "5dab929613fb682b48e4ca6b",
"detail": [
{
"_id": "5dac52a577e09b4acc45718e",
"quantity": 500,
"product": {
"code": 300,
"name": "P2",
"price": 800
},
"undertotal": 5000
}
],
"total": 11000,
"__v": 0
}
I don't want to see id client only. I want to see all content from client inside bill.
I tried to do with populate method, but I haven't results.
So, Which is form to post and populate a nested json relational object in this case?
While posting only clientId is enough.
So your post route can be like this (you both used await and then, which is incorrect, so I refactored it to use only await)
app.post('/api/bills', async (req, res) => {
const { number, type, local, client, detail, total } = req.body;
let existingClient = await Client.findById(req.body.client._id)
if (!existingClient) {
return res.status(404).json({
message: "client not found"
});
}
const bill = new Bill({
number,
date: new Date(),
type,
local,
client: req.body.client._id
detail,
total
})
try {
let newBill = await bill.save();
res.status(201).send(newBill);
} catch (err) {
if (err.name === 'MongoError') {
res.status(409).send(err.message);
}
res.status(500).send(err);
}
});
And in the get route to retrieve all the client info you need to populate it like this:
app.get('/api/bills', async (req, res) => {
try {
const bills = await Bill.find({}).populate("clients");
res.status(200).send(bills);
} catch (err) {
console.log(err);
res.status(500).send(err);
}
}
)

How to insert multiple JSON document in elastic search

Input Data
[{
"_index": "abc",
"_type": "_doc",
"_id": "QAE",
"_score": 6.514091,
"_source": {
"category": "fruits",
"action": "eating",
"metainfo": {
"hash": "nzUZ1ONm0e167p"
},
"createddate": "2019-10-03T12:37:45.297Z"
}},
{
"_index": "abc",
"_type": "_doc",
"_id": "PQR",
"_score": 6.514091,
"_source": {
"category": "Vegetables",
"action": "eating",
"metainfo": {
"hash": "nzUZ1ONm0e167p"
},
"createddate": "2019-10-03T12:37:45.297Z"
}
}-----------------
----------------]
I have around 30,000 records as input data. How to insert this data in a single query. I tried by
var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
host: '********',
log: 'trace'
});
client.index({
index: "abc",
body: ****input data*****
}).then((res) => {
console.log(res);
}, (err) => {
console.log("err", err);
});
In this code, send input data in the body. but it returns an error. Please suggest to me.
This seems like what are you looking for:
'use strict'
require('array.prototype.flatmap').shim()
const { Client } = require('#elastic/elasticsearch')
const client = new Client({
node: 'http://localhost:9200'
})
async function run () {
await client.indices.create({
index: 'tweets',
body: {
mappings: {
properties: {
id: { type: 'integer' },
text: { type: 'text' },
user: { type: 'keyword' },
time: { type: 'date' }
}
}
}
}, { ignore: [400] })
const dataset = [{
id: 1,
text: 'If I fall, don\'t bring me back.',
user: 'jon',
date: new Date()
}, {
id: 2,
text: 'Winter is coming',
user: 'ned',
date: new Date()
}, {
id: 3,
text: 'A Lannister always pays his debts.',
user: 'tyrion',
date: new Date()
}, {
id: 4,
text: 'I am the blood of the dragon.',
user: 'daenerys',
date: new Date()
}, {
id: 5, // change this value to a string to see the bulk response with errors
text: 'A girl is Arya Stark of Winterfell. And I\'m going home.',
user: 'arya',
date: new Date()
}]
// The major part is below:
const body = dataset.flatMap(doc => [{ index: { _index: 'tweets' } }, doc])
const { body: bulkResponse } = await client.bulk({ refresh: true, body })
//
if (bulkResponse.errors) {
const erroredDocuments = []
// The items array has the same order of the dataset we just indexed.
// The presence of the `error` key indicates that the operation
// that we did for the document has failed.
bulkResponse.items.forEach((action, i) => {
const operation = Object.keys(action)[0]
if (action[operation].error) {
erroredDocuments.push({
// If the status is 429 it means that you can retry the document,
// otherwise it's very likely a mapping error, and you should
// fix the document before to try it again.
status: action[operation].status,
error: action[operation].error,
operation: body[i * 2],
document: body[i * 2 + 1]
})
}
})
console.log(erroredDocuments)
}
const { body: count } = await client.count({ index: 'tweets' })
console.log(count)
}
run().catch(console.log)
Reference link: https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/bulk_examples.html

Mongoose populate returning empty array

I am trying to use mongoose populate function but in response I am getting empty array, I have seen multiple posts regarding this
var MerchantSchema = new mongoose.Schema({
packages: [{ $type: mongoose.Schema.Types.ObjectId, ref: 'Package'}]
},
{
typeKey: '$type',
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at'}
});
module.exports = mongoose.model('Merchant', MerchantSchema);
This is my schema definition for the base model.
var mongoose = require('mongoose');
var PackageSchema = new mongoose.Schema({
merchant_id: { type: mongoose.Schema.Types.ObjectId, ref: 'Merchant' },
name: String,
original_price: Number,
discounted_price: Number
});
module.exports = mongoose.model('Package', PackageSchema);
And this is the model I am referring to. The data inside the Package model and Merchant model is being saved just fine.
Merchant document
Package document
But if I query using populate function I am being returned an empty string
Merchant
.findById( req.params.id, 'packages')
.populate('packages')
.exec(function (err, merchant) {
if (err)
next(err);
else
res.status(200).json(merchant);
});
Output:
{
"_id": "579b3b2dc2e8d61c0ecd2731",
"packages": []
}
Can anyone help me
Update:
Ok something really odd is happening. If I try to populate the Package document with merchant_id it is working but not the other way around.
Package
.find()
.populate('merchant_id')
.exec(function (err, packages) {
if(err)
next(err);
else
res.status(200).json(packages);
});
Output:
[
{
"_id": "579b3b51c2e8d61c0ecd2732",
"name": "Hair + Nails",
"original_price": 600,
"discounted_price": 400,
"merchant_id": {
"_id": "579b3b2dc2e8d61c0ecd2731",
"updated_at": "2016-07-29T11:17:37.474Z",
"created_at": "2016-07-29T11:17:01.216Z",
"name": "VLCC",
"logo_image": "http://vlccwellness.com/India/wp-content/uploads/2015/09/logo1.png?",
"cover_image": "http://image3.mouthshut.com/images/imagesp/925053993s.jpg?",
"__v": 1,
"tags": [],
"packages": [
"579b3b51c2e8d61c0ecd2732"
],
"work_hours": {
"opening_time": 1000,
"closing_time": 2100,
"holiday": "Sunday"
},
"information": {
"description": "Lorem Ipsum",
"gender": "Men",
"services": [
"Hair"
],
Use type insted of $type in MerchantSchema.
var MerchantSchema = new mongoose.Schema({
packages: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Package'}]
},
{
typeKey: '$type',
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at'}
});
module.exports = mongoose.model('Merchant', MerchantSchema);
Verify there is an array of ObjectId against packages in your Merchant document.
Can you try by removing second parameter from the findBbyId:
Merchant
.findById( req.params.id)
.populate('packages')
.exec(function (err, merchant) {
if (err)
next(err);
else
res.status(200).json(merchant);
});
Well because your packages field on your Schema is an array you should populate it as an array as well.
Try
.populate([
{path:'packages', model:Package}
])
wher Package is the instance of your Package Model.
Make sure that packages array in Merchant schema contains ObjectIds of type string, (not number). You can ensure this with something like:
merchant.packages.map(r => { r._id = r._id + ''; });

Using pull in mongoose model

Should this work? I am trying to remove a single subdocument (following) from a document (this) in the UserSchema model.
UserSchema.methods.unFollow = function( id ) {
var user = this
return Q.Promise( function ( resolve, reject, notify ) {
var unFollow = user.following.pull( { 'user': id } )
console.log( unFollow )
user.save( function ( error, result ) {
resolve( result )
})
})
}
These are the schemas:
var Follows = new mongoose.Schema({
user: String,
added: Number
})
var UserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true
},
following: [ Follows ]
})
user-controller.js
/*
Unfollow user.
*/
exports.unFollow = function ( req, res ) {
User.findOne( { token: req.token }, function ( error, user ) {
user.unfollow( req.body.id )
.onResolve( function ( err, result ) {
if ( err || !result ) return res.status( 500 ).json( "User could not be unfollowed." )
return res.status( 200 ).json( "User unfollowed." )
})
})
}
user-model.js
/*
Unfollow a user.
*/
UserSchema.method( 'unfollow', function unfollow ( id ) {
this.following.pull( { user: id } )
return this.save()
})
You generally assign methods using the method function:
UserSchema.method('unFollow', function unFollow(id) {
var user = this;
user.following.pull({_id: id});
// Returns a promise in Mongoose 4.X
return user.save();
});
Also, as noted, you don't need to use Q as save will return a mongoose promise.
UPDATE: Mongoose's array pull method will work with matching primitive values but with subdocument objects it will only match on _id.
UPDATE #2: I just noticed your updated question shows that your controller is doing a lookup first, modifying the returned document and then saving the document back to the server. Why not create a static rather than a method to do what you want? This has the added bonus of being a single call to the DB rather than two per operation.
Example:
UserSchema.static('unfollow', function unfollow(token, id, cb) {
var User = this;
// Returns a promise in Mongoose 4.X
// or call cb if provided
return User.findOneAndUpdate({token: token}, {$pull: {follows: {user: id}}}, {new: true}).exec(cb);
});
User.unfollow(req.token, req.body.id).onResolve(function (err, result) {
if (err || !result) { return res.status(500).json({msg: 'User could not be unfollowed.'}); }
return res.status(200).json({msg: 'User unfollowed.'})
});
Bonus follow static:
UserSchema.static('follow', function follow(token, id, cb) {
var User = this;
// Returns a promise in Mongoose 4.X
// or call cb if provided
return User.findOneAndUpdate({token: token}, {$push: {follows: {user: id}}}, {new: true}).exec(cb);
});
User.follow(req.token, req.body.id).onResolve(function (err, result) {
if (err || !result) { return res.status(500).json({msg: 'User could not be followed.'}); }
return res.status(200).json({msg: 'User followed.'})
});
NOTE: Used in "mongoose": "^5.12.13".
As for today June 22nd, 2021, you can use $in and $pull mongodb operators to remove items from an array of documents :
Parent Document :
{
"name": "June Grocery",
"description": "Some description",
"createdDate": "2021-06-09T20:17:29.029Z",
"_id": "60c5f64f0041190ad312b419",
"items": [],
"budget": 1500,
"owner": "60a97ea7c4d629866c1d99d1",
}
Documents in Items array :
{
"category": "Fruits",
"bought": false,
"id": "60ada26be8bdbf195887acc1",
"name": "Kiwi",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92dd67ae0934c8dfce126",
"name": "Toilet Paper",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92fe97ae0934c8dfce127",
"name": "Toothpaste",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b92ffb7ae0934c8dfce128",
"name": "Mouthwash",
"price": 0,
"quantity": 1
},
{
"category": "Toiletry",
"bought": false,
"id": "60b931fa7ae0934c8dfce12d",
"name": "Body Soap",
"price": 0,
"quantity": 1
},
{
"category": "Fruit",
"bought": false,
"id": "60b9300c7ae0934c8dfce129",
"name": "Banana",
"price": 0,
"quantity": 1
},
{
"category": "Vegetable",
"bought": false,
"id": "60b930347ae0934c8dfce12a",
"name": "Sombe",
"price": 0,
"quantity": 1
},
Query :
MyModel.updateMany(
{ _id: yourDocumentId },
{ $pull: { items: { id: { $in: itemIds } } } },
{ multi: true }
);
Note: ItemIds is an array of ObjectId. See below :
[
'60ada26be8bdbf195887acc1',
'60b930347ae0934c8dfce12a',
'60b9300c7ae0934c8dfce129'
]

Resources