meteor - How to update subscription documents? - mongodb

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.

Related

Loopback - How to use bulkUpdate method

I'm using Loopback v3 currently and wanted to upsert many records at once in a collection; I found this method bulkUpsert from the documentation (http://apidocs.loopback.io/loopback/#persistedmodel-bulkupdate) but I couldn't figure out how to make it work.
How can I create the updates array from createUpdates() method as mentioned in the documentation? Can anyone help me with a simple example of using this method?
There is an alternative way to do the bulkUpdate method, found in Stackoverflow MongoDB aggregation on Loopback
A mixin can be easily created and reused over the Models. My sample code of bulkUpsert mixin is below:
Model.bulkUpsert = function(body, cb) {
try {
Model.getDataSource().connector.connect(async (err, db) => {
if (err) {
return cb(err);
}
// Define variable to hold the description of the first set of validation errors found
let validationErrors = '';
// Build array of updateOne objects used for MongoDB connector's bulkWrite method
const updateOneArray = [];
// Loop through all body content and stop the loop if a validation error is found
const hasError = body.some(row => {
// Check if it is a valid model instance
const instance = new Model(row);
if (!instance.isValid()) {
// A validation error has been found
validationErrors = JSON.stringify(instance.errors);
// By returning true we stop/break the loop
return true;
}
// Remove ID in the row
const data = JSON.stringify(row);
delete data.id;
// Push into the update array
updateOneArray.push({
updateOne: {
filter: { _id: row.id },
update: { $set: Object.assign({ _id: row.id }, data) },
upsert: true
}
});
// No validation error found
return false;
});
// Check if a validation error was found while looping through the body content
if (hasError) {
return cb(new Error(validationErrors));
}
// No validation data error was found
// Get database collection for model
const collection = db.collection(Model.name);
// Execute Bulk operation
return collection.bulkWrite(updateOneArray, {}, (err, res) => {
// Check if the process failed
if (err) {
console.err('The bulk upsert finished unsuccessfully', err);
return cb(err);
}
// Check if there were errors updating any record
if (res.hasWriteErrors()) {
console.error(`The bulk upsert had ${res.getWriteErrorCount()} errors`, res.getWriteErrors());
}
// Finished successfully, return result
return cb(null, {
received: body.length,
handled: res.upsertedCount + res.insertedCount + res.matchedCount
});
});
});
}
catch (err) {
console.error('A critical error occurred while doing bulk upsert', err);
return cb(err);
}
return null;
};
Ref: Mongodb query documentation

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;

Add new data from restful api to angularjs scope

I'm trying to create a list with endless scroll in angularjs. For this I need to fetch new data from an api and then append it to the existing results of a scope in angularjs. I have tried several methods, but none of them worked so far.
Currently this is my controller:
userControllers.controller('userListCtrl', ['$scope', 'User',
function($scope, User) {
$scope.users = User.query();
$scope.$watch('users');
$scope.orderProp = 'name';
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
var promise = user.query();
$scope.users = $scope.users.concat(promise);
}
}, false);
}
]);
And this is my service:
userServices.factory('User', ['$resource',
function($resource) {
return $resource('api/users', {}, {
query: {
method: 'GET',
isArray: true
}
});
}
]);
How do I append new results to the scope instead of replacing the old ones?
I think you may need to use $scope.apply()
When the promise returns, because it isnt
Part of the angular execution loop.
Try something like:
User.query().then(function(){
$scope.apply(function(result){
// concat new users
});
});
The following code did the trick:
$scope.fetch = function() {
// Use User.query().$promise.then(...) to parse the results
User.query().$promise.then(function(result) {
for(var i in result) {
// There is more data in the result than just the users, so check types.
if(result[i] instanceof User) {
// Never concat and set the results, just append them.
$scope.users.push(result[i]);
}
}
});
};
window.addEventListener('scroll', function(event) {
if (document.body.offsetHeight < window.scrollY +
document.documentElement.clientHeight + 300) {
$scope.fetch();
}
}, false);

Drop MongoDB database before running Mocha test

If I try to drop the database using after (at the end of my tests) it works.
If I try the following:
var db = mongoose.connect('mongodb://localhost/db-test')
describe('Database', function() {
before(function (done) {
db.connection.db.dropDatabase(function(){
done()
})
})
...
it does not drop the DB. what is going on? I would prefer dropping the db before starting testing -- so that after testing I can explore the db.
solved by connect in another define.. not sure if ideal.
describe('Init', function() {
before(function (done) {
mongoose.connect('mongodb://localhost/db-test', function(){
mongoose.connection.db.dropDatabase(function(){
done()
})
})
})
describe('Database', function() {
I implemented it a bit different.
I removed all documents in the "before" hook - found it a lot faster than dropDatabase().
I used Promise.all() to make sure all documents were removed before exiting the hook.
beforeEach(function (done) {
function clearDB() {
var promises = [
Model1.remove().exec(),
Model2.remove().exec(),
Model3.remove().exec()
];
Promise.all(promises)
.then(function () {
done();
})
}
if (mongoose.connection.readyState === 0) {
mongoose.connect(config.dbUrl, function (err) {
if (err) {
throw err;
}
return clearDB();
});
} else {
return clearDB();
}
});