mongoosejs pre save hook on array field - mongodb

I want to have all the category lower case
I tried this:
ArticleSchema.pre('save', function(next) {
_.map(this.categories, function(category) {
console.log(category.toLowerCase());
return category.toLowerCase();
});
next();
});
but it doesn't work (If I insert ie PHP,JAVA I find PHP,JAVA)
What's wrong ?

map() doesn't modify the original array, you need to assign the result to the property:
ArticleSchema.pre('save', function(next) {
this.categories = _.map(this.categories, function(category) {
console.log(category.toLowerCase());
return category.toLowerCase();
});
next();
});

Related

.find() returning nothing even when data exists

I've a mongo database with 3 collections for 3 different kind of users as User,Partner,Admin. Whenever a new user of any type signup I'm searching all three collections to check if username and email exist already. I'm trying to achieve this by calling a function as:
function checkAttribute(attr,val,callback){
User.find({attr: val},function(err,user){
if(err){
console.log(err);
}else{
if(user.length === 0){
Partner.find({attr: val},function(err,partner){
if(err){
console.log(err);
}else{
if(partner.length === 0){
Admin.find({attr: val},function(err,admin){
if(err){
console.log(err);
}else{
if(admin.length === 0){
return callback(null,true);
}else{
return callback(null,false);
}
}
});
}else{
return callback(null,false);
}
}
});
}else{
return callback(null,false);
}
}
});
};
Calling function line:
checkAttribute("username",newUser.username,function(error,response){
.......
});
But this is not working as it returns true always even when users with passed username/email exists already. I am unable to find the problem. Any one knows why this is happening?
Thanks in advance.
Since you are passing in the attribute as a variable in the function parameters, the query document
{ attr: val } is an object with the key "attr", not the dynamic attribute you pass in.
To fix this, you need to use computed property names in your query object as
{ [attr]: val }
Also, the function can use async/await pattern to be more readable and for the purpose of finding if a document exist findOne does the job so
well as it returns a document if it exists and null otherwise.
So your function can be refactored as
async function checkAttribute(attr, val, callback) {
try {
const query = { [attr]: val }
const user = await User.findOne(query).exec()
const partner = await Partner.findOne(query).exec()
const admin = await Admin.findOne(query).exec()
const found = (user || partner || admin) ? true: false
return callback(null, found)
} catch (err) {
console.error(err)
return callback(err, null)
}
};
attr: in your queries will search for a db field called attr. If you want to use the function parameter attr, use [attr]: as the key.
Example:
attr = 'username'
User.find({ [attr]: val }, function (err, user) {
if (err) {
console.log(err);
}
})
This is a feature available since ES6 so should work fine. See the docs here for more info

Save records into the same collection in Mongo DB using Meteor js

Being new to Meteor JS, I'm confused on how to update a record. I have 2 templates AddSchoolLayout and Schoolcontactlayout, AddSchoollayout creates the record in Mongo db, now, for the same school I want to add its address still on the same DB NewSchoolDB but as I tried this I keep getting the error below about wrong ID. What wrong am I to right?
Note that my language might not correlate with Mongo's; I'm still fresh, coming from the SQL background.
This is the method.js where the record is been saved.
//methods.js
if (Meteor.isServer) {
Meteor.methods({
SchoolRegister: function (phone, schoolemail) {
if (!Meteor.userId()) {
throw new Meteor.error('Not authorized');
return false;
}else{
NewSchoolDB.insert({
authorId: Meteor.userId(),
phone: phone,
schoolemail
});
}
}
});
}
This is the event for saving a new school
//add school
Template.AddSchoolLayout.events({
'submit .addnewschool': function (event, template) {
event.preventDefault();
var newschoolname = trimInput(event.target.newschoolname.value);
if (isNotEmpty(newschoolname)) {
Meteor.call('SchoolRegister', newschoolname,
function (error, response) {
if (error) {
Bert.alert(error.reason, 'danger', 'growl-top-right');
return false;
}else{
Bert.alert("School successfully created", "success", "growl-top-right");
FlowRouter.redirect('/schoolcontact');
}
});
}
return false;
}
});
This is where I want to update the school address
//school contact
Template.SchoolContactLayout.events({
'submit .contactschool': function (event) {
event.preventDefault();
var phone = trimInput(event.target.phone.value);
if (isNotEmpty(phone)) {
Meteor.call('SchoolRegister', phone, function (error, response) {
if (error) {
Bert.alert(error.reason, 'danger', 'growl-top-right');
return false;
}else{
Bert.alert('School address updated successfully', 'success', 'growl-top-right');
FlowRouter.redirect('/logo-upload');
}
});
}
return false;
}
});
Error logged on the console
I20170524-17:44:14.051(1)? at packages/ddp-server/livedata_server.js:559:43
I20170524-17:51:54.678(1)? Exception from sub NewSchoolDB id onFTu2j3xRmbqC5WF TypeError: this.userId is not a function
I20170524-17:51:54.710(1)? at [object Object]._handler (lib/pulbish/published.js:3:13)
I20170524-17:51:54.712(1)? at maybeAuditArgumentChecks (packages/ddp-server/livedata_server.js:1737:12)
I20170524-17:51:54.713(1)? at [object Object]._.extend._runHandler (packages/ddp-server/livedata_server.js:1035:17)
I20170524-17:51:54.714(1)? at [object Object]._.extend._startSubscription (packages/ddp-server/livedata_server.js:853:9)
I20170524-17:51:54.715(1)? at [object Object]._.extend.protocol_handlers.sub (packages/ddp-server/livedata_server.js:625:12)
I20170524-17:51:54.719(1)? at packages/ddp-server/livedata_server.js:559:43
Your SchoolRegister method accepts 2 arguments: phone and schoolmail. When you create the school you call the methods with one argument called newschoolname. So something is wrong here with your naming convention, but it shouldn't really matter regarding your question.
With MongoDB, you use insert to add a new record to your collection. If you need to update it, use update. So one way to solve your problem is to:
In AddSchoolLayout, call your method to insert the document the first time. NewSchoolDB.insert(...) will return the ID of the created record. Keep that ID and pass it to your next page, for exemple in the URL.
In your next page, SchoolContactLayout, you need to call a new method which is going to update your school, using the ID returned by the previous method. This new method will look something like this:
SchoolUpdate: function (schoolId, phone) {
if (!Meteor.userId()) {
throw new Meteor.error('Not authorized');
return false;
}else{
NewSchoolDB.update(schoolId, { $set: { phone } });
}
}
The first argument of the update MongoDB function is the ID of the record you want to update. The second arguments are the modifiers you want to use to update your record. Here is the full list of modifier you can use: update modifiers
EDIT: how to pass ID from one layout to another:
I didn't use FlowRouter for a while and I can't test it right now so you may have to do some correction, but here is how I would do it to give you an idea:
First you need to define your route SchoolContactLayout with something like this:
FlowRouter.route('/schoolcontact/:schoolId', {
name: 'schoolContact',
});
This adds a schoolId parameter to the route.
In your SchoolRegister method, get the return ID and return it:
var id = NewSchoolDB.insert({
authorId: Meteor.userId(),
schooleName
});
return { id }
Edit your redirection FlowRouter.redirect('/schoolcontact'); with FlowRouter.go('/schoolcontact/' + response.id);
You can then edit your contactSchool event with something like this:
Template.SchoolContactLayout.events({
'submit .contactschool': function (event) {
event.preventDefault();
var schoolId = FlowRouter.getParam('schoolId');
var phone = trimInput(event.target.phone.value);
if (isNotEmpty(phone)) {
Meteor.call('SchoolUpdate', schoolId ,phone, function (error, response) {
if (error) {
Bert.alert(error.reason, 'danger', 'growl-top-right');
return false;
}else{
Bert.alert('School address updated successfully', 'success',
'growl-top-right');
FlowRouter.redirect('/logo-upload');
}
});
}
return false;
}
});
Notice the var schoolId = FlowRouter.getParam('schoolId'); to get the ID from URL parameter so I can use it in the update method.

Waiting for meteor cursor in method

I have a large aggrogate query that required me to pass "allowDiskUse: true" as an option. This would not work with the aggegate as described here:
https://github.com/meteorhacks/meteor-aggregate/issues/11
My meteor method is defined here. When I call the method I need to wait for ondata to complete before anything is returned to the client, but nothing I try allows me to get that data in a safe way up to the front end.
Meteor.methods({
'getSummary': function (dept,startDate,endDate,filterType) {
f = myQuery(startdate,enddate,dayFinalGroup);
f.on("data", Meteor.bindEnvironment(function(row) {
//load an array or something here to return
}));
f.once("end", Meteor.bindEnvironment(function() {
// tidy up, in my case end the stream
}));
//here I'd return the array loaded
},
});
This is my front end.
Meteor.call(
'getSummary',0,Session.get('start_date'),Session.get('end_date'),1,
function(error, result){
if(error){
console.log(error);
} else {
Session.set('sumTotals',result);
}
}
);
Finally Got it. I utilized wrapSync
'getSummary': function (dept,startDate,endDate,filterType) {
console.log(dept);
console.log(startDate);
console.log(endDate);
console.log(filterType);
var startdate = new Date(startDate);
var enddate = new Date(endDate);
var arr = [];
f = myQuery(startdate,enddate,dayFinalGroup);
var fetchCursor = Meteor.wrapAsync(function fetchCursor (cursor, cb) {
cursor.each(function (err, doc) {
if (err) return cb(err);
if (!doc) return cb(null, { done: true }); // no more documents
arr.push(doc);
});
});
var myData = fetchCursor(f);
return arr;

meteor - How to update subscription documents?

I subscribe some collection from server. After I try to delete one document from client side it shows remove failed: Access denied. so I tried to delete it from server side by Meteor.call it works fine but client side has same number of documents.
Below code will explain you better.
ClientJS:
Template.Message.onCreated(function () {
this.autorun(function () {
this.subscription = Meteor.subscribe('mymessage');
}.bind(this));
});
Template.Message.onRendered(function () {
this.autorun(function () {
if (this.subscription.ready()) {
console.log(Message.find().count()); //10
}
}.bind(this));
});
ServerJS:
Meteor.publish('mymessage', function() {
console.log(Message.find().count()); //10
return Message.find();
});
In a click event
ClientJS:
Meteor.call("deletemsg", this._id._str, function(error, result){
if(!error){
console.log(Message.find().count()); // 10, Want to update document here.
}
});
Serverjs
Meteor.methods({
deletemsg: function (delmsg) {
if(Message.remove({"_id":new Mongo.ObjectID(delmsg)})){
console.log(Message.find().count()); //9
return true;
} else {
throw new Meteor.Error("some error message");
}
}
});
Note : I am using existing Mongodb.
Your error is probably related to your configuration of the native rules allow and deny. You should have somewhere on your server a piece of code looking like that (Message being your collection name):
Message.allow({
insert: function (userId, doc) {
//allow rule
},
update: function (userId, doc, fields, modifier) {
//allow rule
},
remove: function (userId, doc) {
//allow rule
}
});
Or an equivalent with deny. It looks like your current user is not allowed to delete (i.e. remove) messages from the collection.
Quick sidenote: you don't need to wrap your subscriptions in an autorun. If you use iron-router, you can use the built in functions to subscribe. In your routes options, you can add something like this:
action: function() {
if(this.isReady()) { this.render(); } else { this.render("loading");}
},
isReady: function() {
var subs = [
Meteor.subscribe("yourPublication")
];
var ready = true;
_.each(subs, function(sub) {
if(!sub.ready())
ready = false;
});
return ready;
},
You don't need to use _str as _id is already string.
Client JS
Meteor.call("deletemsg", this._id, function(error, result){ //Remove _str from here
if(!error){
console.log(Message.find().count()); // 10, Want to update document here.
}
});
Server JS
When you delete document, you need to pass only id, not object.
Meteor.methods({
deletemsg: function (delmsg) {
if(Message.remove(delmsg)){
console.log(Message.find().count()); //9
return true;
} else {
throw new Meteor.Error("some error message");
}
}
});
Allowing CRUD operations.
if above method doesnt work, try allowing CRUD operations for that collections from server block. Here is documentation.

Using $inc to increment a document property with Mongoose

I would like to increment the views count by 1 each time my document is accessed. So far, my code is:
Document
.find({})
.sort('date', -1)
.limit(limit)
.exec();
Where does $inc fit in here?
Never used mongoose but quickly looking over the docs here it seems like this will work for you:
# create query conditions and update variables
var conditions = { },
update = { $inc: { views: 1 }};
# update documents matching condition
Model.update(conditions, update).limit(limit).sort('date', -1).exec();
Cheers and good luck!
I ran into another problem, which is kind of related to $inc.. So I'll post it here as it might help somebody else. I have the following code:
var Schema = require('models/schema.js');
var exports = module.exports = {};
exports.increase = function(id, key, amount, callback){
Schema.findByIdAndUpdate(id, { $inc: { key: amount }}, function(err, data){
//error handling
}
}
from a different module I would call something like
var saver = require('./saver.js');
saver.increase('555f49f1f9e81ecaf14f4748', 'counter', 1, function(err,data){
//error handling
}
However, this would not increase the desired counter. Apparently it is not allowed to directly pass the key into the update object. This has something to do with the syntax for string literals in object field names. The solution was to define the update object like this:
exports.increase = function(id, key, amount, callback){
var update = {};
update['$inc'] = {};
update['$inc'][key] = amount;
Schema.findByIdAndUpdate(id, update, function(err, data){
//error handling
}
}
Works for me (mongoose 5.7)
blogRouter.put("/:id", async (request, response) => {
try {
const updatedBlog = await Blog.findByIdAndUpdate(
request.params.id,
{
$inc: { likes: 1 }
},
{ new: true } //to return the new document
);
response.json(updatedBlog);
} catch (error) {
response.status(400).end();
}
});