Meteor how to get the result of Collection.update on success or failure? - meteor

I have a collection that is getting updated in an event handler and which is updating the collection and I would like to get the result of the update if it was a success or failure so I can do some logic based on its result. i.e. reset session values etc.
I have always been just testing the db action itself inside of an if block for inserts which worked fine however this does not seem to be working for update.
Template.customers_update.events({
'click a#cancel, click button#close' : function(event) {
event.preventDefault();
Session.set("editCustomer", false);
Session.set("customerId", null);
},
'click input[type=submit], submit form#create_customer' : function (event) {
event.preventDefault();
var customer_name = $("#customer_name").val();
var customer_address = $("#customer_address").val();
var customer_city = $("#customer_city").val();
var customer_state = $("#customer_state").val();
var customer_zip = $("#customer_zip").val();
var customer_phone = $("#customer_phone").val();
var customer_fax = $("#customer_fax").val();
var customer_eda = $("#eda_number").val();
var customer_duns = $("#duns_number").val();
if (Customers.update(Session.get("customerId"), {$set: {user_id: Meteor.user()._id, name: customer_name, address: customer_address, city: customer_city, state: customer_state, zip: customer_zip, phone: customer_phone, fax: customer_fax, eda_number: customer_eda, duns_number: customer_duns}})) {
console.log("Update Sucsess");
Session.set("editCustomer", false);
Session.set("customerId", null);
}
}
});
and in the server it is set to allow and return true
Customers.allow({
insert: function (userID, customer) {
return userID === customer.user_id;
},
update: function (userID, customer) {
return userID === customer.user_id;
},
remove: function (userID, customer) {
return userID === customer.user_id;
}
});

Use the third argument callback (docs)
callback Function
Optional. If present, called with an error object as its argument.

Your code is probably not working because the .update() only throws an exception on the server. From the docs:
On the server, if you don't provide a callback, then update blocks until the database acknowledges the write, or throws an exception if something went wrong. If you do provide a callback, update returns immediately. Once the update completes, the callback is called with a single error argument in the case of failure, or no arguments if the update was successful.
On the client, update never blocks. If you do not provide a callback and the update fails on the server, then Meteor will log a warning to the console. If you provide a callback, Meteor will call that function with an error argument if there was an error, or no arguments if the update was successful.
Change it to:
var updateQuery = {$set: {user_id: Meteor.user()._id, name: customer_name, address: customer_address, city: customer_city, state: customer_state, zip: customer_zip, phone: customer_phone, fax: customer_fax, eda_number: customer_eda, duns_number: customer_duns}}
Customers.update(Session.get("customerId"), updateQuery, function (error) {
//on error do this
});

Related

Meteor Async ValidatedMethod gets called with function parameters undefined

someMethod = new ValidatedMethod({
name: 'someMethodName',
validate: new SimpleSchema({
subId: {type: String, min:1},
planId: {type: String}
}).validator(),
async run(params){
try{
//params is undefined
}
}
});
Using async run(params) causes params to be undefined (seems like the context switches to Global context). Removing the async works fine (except that I cannot use await in the method body anymore obviously).
Why is this, and how can I still use await inside a ValidatedMethod?
Note1 : I call the method from the client like so -- and get the same result if I try to use a regular Meteor.methods({}) definition. I am calling the method using Meteor.apply from the client
ClientHelpers.callWithPromise = function(methodName, methodArgs){
//methodArgs must be an array
return new Promise(function(resolve, reject){
Meteor.apply(methodName, methodArgs, {wait:true}, function(error, result){
if (error){
reject(error);
}
console.log(result);
resolve(result);
});
});
}
Then, calling on client (am sure paramsObject is correct):
var myResult = await ClientHelpers.callWithPromise('someMethodName', [paramsObject]);
Note 2: I have also traced it through to the internals of Meteor.apply , where it is in fact sending the paramsObject over DDP, in debug session:
// Sends the DDP stringification of the given message object
_send(obj) {
this._stream.send(DDPCommon.stringifyDDP(obj));
}
Many thanks for any insight.

Tracker autorun using findone

I have this piece of code in client side:
Tracker.autorun(function () {
if (params && params._id) {
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
}
}
});
params will be passed into the url. So, initially we won't have the department data and the findOne method will return null, and then later on, when data arrives, we can find the department object.
But if user enters an invalid id, we need to return them 404. Using tracker autorun, how can I distinguish between 2 cases:
a. Data is not there yet, so findOne returns null
b. There is no such data, even in server's mongodb, so findOne will also returns null.
For case a, tracker autorun will work fine, but for case b, I need to know to return 404
I would suggest you to subscribe to data inside template, like below so you know when subscriptions are ready, then you can check data exists or not
Template.myTemplate.onCreated(function onCreated() {
const self = this;
const id = FlowRouter.getParam('_id');
self.subscribe('department', id);
});
Template.myTemplate.onRendered(function onRendered() {
const self = this;
// this will run after subscribe completes sending records to client
if (self.subscriptionsReady()) {
const id = FlowRouter.getParam('_id');
const dept = Department.findOne({ _id: params._id }) || Department.findOne({ name: params._id });
if (dept) {
// found data in db
} else {
// 404 - no department found in db
}
}
});
If you are using Iron-Router, you may try this hack.
Router.route('/stores', function() {
this.render('stores', {});
}, {
waitOn: function() {
return [
Meteor.subscribe('stores_db')
];
}
});
The sample code above will wait for the subscription "stores_db" to complete, before rendering anyhing. Then you can use your findOne logic no problems, ensuring that all documents are availble. This suits your situation.
This is what I used to do before I completely understand MeteorJS publications and subscriptions. I do not recommend my solution, it is very bad to user experience. Users will see the page loading forever while the documents are being download. #Sasikanth gave the correct implementation.

Meteor method create insert hook and bind userId on the server

I implemented a hook function, where I attach some createdAt and updatedAt fields to the doc that is inserted to a collection. I can attach this to any collection like this:
export const insertHook = function (doc) {
try {
const user = Meteor.user();
doc.createdBy = user && user._id ? user._id : null;
doc.createdAt = new Date().getTime();
} catch (e) {
console.err(e);
}
};
Attaching the hook to the collection is basically passing it via a third option in the constructor:
class HookedCollection extends Mongo.Collection {
constructor(name, options, hooks={}) {
super(name, options);
this.insertHook = hooks.insertHook;
}
insert(doc, callback) {
if (this.insertHook && Meteor.isServer)
this.insertHook.call(this, doc);
}
}
export const MyDocs = new HookedCollection("mydocs", {}, {insertHook});
In a Meteor method I just do a normal insert:
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc);
}
});
Which creates basically the following error:
Error: Meteor.userId can only be invoked in method calls or publications.
I tried several ways of bind but always ended up in this error. Is there really no way at all to bind the userId to the function?
According to Meteor docs Meteor.userId() is available anywhere but publish functions (Server side Publish function).
You aren't using Meteor.userId() directly in the method but in a callback (see discussion in this github issue). You can pass the userId information to your callback function as a parameter from the method, for example:
// Using Meteor.userId()
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, Meteor.userId());
}
});
// Or using this.userId
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, this.userId());
}
});
As a general rule use Meteor.userId() in the client (that queries the database) and this.userId in the server. More information in this other question Meteor - Why should I use this.userId over Meteor.userId() whenever possible? and in Meteor forums

Getting a username from ID without autopublish

I just got done with the rough draft of my app, and thought it was time to remove autopublish and insecure mode. I started transfering all the stray update and insert methods I had been calling on the client to methods. But now I'm having trouble returning a username from an ID.
My function before: (that worked, until I removed autopublish)
challenger: function() {
var postId = Session.get('activePost');
var post = Posts.findOne(postId);
if (post.challenger !== null) {
var challenger = Meteor.users.findOne(post.challenger);
return challenger.username;
}
return false;
}
Now what I'm trying:
Template.lobby.helpers({
challenger: function() {
var postId = Session.get('activePost');
var post = Posts.findOne(postId);
if (post.challenger !== null) {
var userId = post.challenger;
Meteor.call('getUsername', userId, function (err, result) {
if (err) {
console.log(err);
}
return result;
});
}
return false;
},
Using:
Meteor.methods({
getUsername: function(userId) {
var user = Meteor.users.findOne({_id: userId});
var username = user.username;
return username;
},
...
})
I have tried blocking the code, returning values only once they're defined, and console.logging in the call-callback (which returned the correct username to console, but the view remained unchanged)
Hoping someone can find the obvious mistake I'm making, because I've tried for 3 hours now and I can't figure out why the value would be returned in console but not returned to the template.
Helpers need to run synchronously and should not have any side effects. Instead of calling a method to retrieve the user, you should ensure the user(s) you need for that route/template are published. For example your router could wait on subscriptions for both the active post and the post's challenger. Once the client has the necessary documents, you can revert to your original code.

Collection2, insert using method, exception from unique constraint not caught

I create a new project:
$ mrt create sandbox
$ mrt remove autopublish
$ mrt add collection2
And use the following code to create a simple collection with a unique constraint on a key
SandBoxCollection = new Meteor.Collection('sandboxcoll', {
schema: new SimpleSchema({
title: {
type: String,
min: 3,
unique: true,
index: true
}
})
});
if (Meteor.isServer) {
Meteor.publish('sandboxpub', function() {
return SandBoxCollection.find();
});
}
if (Meteor.isClient) {
Meteor.subscribe('sandboxpub');
}
Meteor.methods({
create: function(doc) {
var docId = SandBoxCollection.insert(doc, {validationContext: 'create'}, function(err, res) {
if (err) {
throw new Meteor.Error(333, SandBoxCollection.simpleSchema().namedContext('create').invalidKeys());
}
return res;
});
return docId;
}
});
I set up a simple collection, pub/sub and a method that I can use for inserts.
Then I use the browser console to issue the following commands
Let's first create a document:
Meteor.call('create', {title: 'abcd01'}, function(e,r){
console.log(e ? e : r);
});
Now let's try inserting a duplicate directly using collection.insert():
SandBoxCollection.insert({title: 'abcd01'}, function(e,r) {
console.log('error: ');
console.log(e);
console.log('errorkeys: ');
console.log(SandBoxCollection.simpleSchema().namedContext().invalidKeys());
console.log('result: ');
console.log(r);
});
We can see a proper 333 error handled by the callback and logged to the console.
Now try inserting a duplicate using the method:
Meteor.call('create', {title: 'abcd01'}, function(e,r){
console.log(e ? e : r);
});
Notice that, unlike the direct insert, the method throws an uncaught exception! Furthermore, the error is thrown from our custom throw and it has error code 333.
Why is this not handled properly? What can I do to mitigate this so that I can do something with the error (notify the user, redirect to the original documents page etc)
As of February 2014, this is an enhancement request on collection2 issue tracker at https://github.com/aldeed/meteor-collection2/issues/59
The current workaround (on the server) is to catch the error separately and feed it into a custom Meteor.Error as in:
if (Meteor.isServer) {
Meteor.methods({
insertDocument: function(collection, document) {
check(collection, String);
check(document, Object);
var documentId = '',
invalidKeys = [];
function doInsert() {
documentId = SandboxProject.Collections[collection + 'Collection'].insert(document, {validationContext: collection + 'Context'});
}
try {
doInsert();
} catch (error) {
invalidKeys = SandboxProject.Collections[collection + 'Collection'].simpleSchema().namedContext(collection + 'Context').invalidKeys();
error.invalidKeys = invalidKeys;
throw new Meteor.Error(333, error);
}
return documentId;
}
});
}
Note: This is a generic insert method that takes the namespaced collection name as a parameter and a document. It is intended to be called from the client side with a callback function which returns either the result as a document id or an error object.

Resources