Meteor: Prevent undefined result for findOne() - javascript

I want to get some information out of a collection - in this example the user collection; but could be anything:
var result = Users.findOne({ _id: Meteor.userId() }).profile.anything;
In some cases the information is missing. For example if the DB is empty, I would get the error Exception in template helper: TypeError: Cannot read property 'profile' of undefined.
What would be the best way to prevent this if I'm using findOne and get an undefined result?
I would do something like:
var temp = Users.findOne({ _id: Meteor.userId() });
if (temp)
var result = temp.profile.anything;
But that looks not very elegant.

I cover this in some detail in my post on guards. This isn't really an issue with meteor or even the find operations - its just defensive programming in JavaScript. The generally accepted practice in cases where a guard may be needed is something like:
x && x.y && x.y.z
which covers the case where one or more of x and y are undefined.
You may run into situations where a long function is predicated on the existence of some piece of data. In those situations you may want to do something like:
user = Meteor.user();
if (!user) return;
...
// rest of function which assumes user is defined

Related

Getting a new id for a firestore doc to add it to the document itself

According to the documentation here, it should be possible get an id for a not-yet-created firestore document, add it the object to be saved, and then persist it like this:
// Add a new document with a generated id.
var newCityRef = db.collection("cities").doc();
// later...
newCityRef.set(data);
In my application, I follow this pattern with the following code:
async createNewProject(projectObject : Project) : Promise<boolean> {
let doc = this.firestore.collection("projects").doc();
projectObject.id = doc.ref.id;
try {
await doc.set(projectObject);
} catch(err) {
console.error(err);
return false;
}
return true;
}
When it runs though, i get an error in the console:
FirebaseError: Function CollectionReference.doc() requires its first argument to be of type non-empty string, but it was: undefined
Can anybody shed any light? I've seen other folks on her referencing this method (using the doc method with no parameters as the solution to the problem, yet others are seeing this error. Is this some kind of API on the Angular implementation of the API, or a problem at the firebase side of things (or did I do something wrong?)
The doc() method returns a DocumentReference, which does not have a ref property but has an id one. Therefore you need to do:
projectObject.id = doc.id;
instead of
projectObject.id = doc.ref.id;

Firebase firestore cloud functions showing Error: Invalid use of type "undefined" as a Firestore argument

I have a project to add currency details into the firestore database and my project is doing with ionic 3
Whenever I add a new document to the collection a trigger function onCreate() will execute and update the document named 'updated'.
But the trigger function always showing an error.
Error: Invalid use of type "undefined" as a Firestore argument.
at Object.exports.customObjectError.val [as customObjectError] (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/validate.js:164:14)
at Function.encodeValue (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/document.js:808:20)
at Function.encodeFields (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/document.js:678:36)
at Function.fromObject (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/document.js:218:55)
at WriteBatch.set (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/write-batch.js:291:39)
at DocumentReference.set (/user_code/node_modules/firebase-admin/node_modules/#google-cloud/firestore/src/reference.js:419:8)
at Object.<anonymous> (/user_code/lib/index.js:28:10)
at next (native)
at /user_code/lib/index.js:7:71
at __awaiter (/user_code/lib/index.js:3:12)
sombody please help..
i have spent lot of time on it.
Here is the code :
import * as functions from 'firebase-functions';
const admin = require('firebase-admin');
admin.initializeApp();
exports.createCurrency = functions.firestore
.document('Exchange/{ExchangeId}')
.onCreate( async (snap, context) => {
const id: string = snap.data().id;
const branchName: string = snap.data().branchName;
const currencyName: string = snap.data().currencyName;
const buyingRate : string = snap.data().buyingRate;
const sellingRate : string = snap.data().sellingRate;
const newUser= admin.
firestore()
.doc(`Exchange/updated`)
.set({
id : id,
branchName : branchName,
currencyName : currencyName,
sellingRate : sellingRate,
buyingRate :buyingRate
});
return newUser;
});
The error message is this:
Invalid use of type "undefined" as a Firestore argument.
You can see in your stack trace that this happens when you call set() with an object on a DocumentReference. It turns out that one of the values you're passing in the object is undefined. Check each of the values that you're passing and make sure all of them have an actual value:
.set({
id : id,
branchName : branchName,
currencyName : currencyName,
sellingRate : sellingRate,
buyingRate :buyingRate
});
It's impossible to tell which one it is from the error message, so you'll have to print them all out do something to check each and every one of them.
When you .set an object but one of the fields in the object is undefined you will get this error.
The problem is that when you use console.log to display the object it does not show the undefined variables so it is difficult to trace.
Use the following instead of console.log to find the element that is causing the problem.
const util = require('util');
console.log(util.inspect(myObject, {showHidden: false, depth: null}));
This will give you an output as follows:
{ origin: 'AMS',
destination: undefined,
duration: 94,
carrier: 'KL',
flight_number: '2977',
departure: '2019-06-11T15:34:00',
arrival: '2019-06-11T17:08:00',
type: 'flight' }
Understanding the issue:
The following error message is relatively clear
Invalid use of type "undefined" as a Firestore argument.
This means, that in your set or update reference method, you've passed an argument, which had value of undefined, which is by definition not a valid Firestore argument.
Fixing the issue:
Personally I find everything much easier to understand with an example.
Let's say my function editStafferPoolPermissions errors with the "Invalid use of type "undefined" as a Firestore argument" error message. Which is defined the following way:
export const editStafferPoolPermissions(data: {
businessId: string,
stafferId: string,
poolPermissions: string[],
areCustomPermissions: boolean,
wage: number,
}) => //...
To find out which argument (or even arguments) it is exactly
Open your Firebase developer console and open the "Functions" tab.
Select the logs tab
Filter out the exact name of the function and check the arguments passed
This allow us to see, which arguments were passed and which weren't.
As you can see, the wage parameter is missing in the oncall invocation of my https cloud function, causing the error to crash. This means I either forgot to pass or am passing the wage parameter incorrectly.
Obviously the undefined argument will wary depending on how your function is defined but hopefully this should be enough for you to get the gist of how to trace and fix the issue. It usually boils down to two options, you either forgot to pass it altogether or are passing it incorrectly from the front-end (or the data is incorrectly structured)
What if I want to allow undefined (optional) arguments?
What most of the answers on internet don't tackle, is the scenario where we might actually leave an argument undefined on purpose.
I actually had a lot of trouble finding this for the longest time, where I had to resort to writing a very shoddy looking cloud funciton full of nested ifs, when I wanted to create one, that would also allow optional parameters as undefined and simply ignore them if they aren't passed.
To continue from our previous example, let's say we changed the wage argument to optional, i.e.
wage?: number
// ... other params
So now, if we call the editStafferPoolPermissions cloud functions, it shouldn't matter whether the wage is passed or not.
Luckily, as of May 29 2020, there has been added a new argument to the SetOptions called ignoreUndefinedProperties, which allows you to simply ignore undefined parameters.
For example, the inside of my editStafferPoolPermissions could look something like this.
await firestore.collection('staffers').doc(stafferId).set({
poolPermissions,
areCustomPositions,
wage,
}, { ignoreUndefinedProperties: true })
Troubleshooting legacy firebase versions
Given this newly added argument is relatively recent and even in my work I was on relatively older codebase which for legacy reasons could not have the most up-to-date firebase version, so for this reason, I needed to create a polyfill, which would mimic the ignoreUndefinedProperties argument.
I've created the following function:
export const ignoreUndefinedSet = async (
// depending on your namespace
// & /types version, you can use firebase.firestore.<name> instead
reference: FirebaseFirestore.DocumentReference,
data: FirebaseFirestore.DocumentData,
options?: FirebaseFirestore.SetOptions,
checkNestedObjects = true,
) => {
const isPlainObject = (val: unknown) =>
typeof val === 'object' && val !== null &&
!(val instanceof Date) && !Array.isArray(val)
const keepDefinedProperties = (
obj: FirebaseFirestore.DocumentData,
nestedCheck = true,
) =>
Object.entries(data).reduce(
(result, [key, value]) => (
value === undefined
? result
: (nestedCheck && isPlainObject(value))
? Object.assign(result, { [key]: keepDefinedProperties(value) })
: Object.assign(result, { [key]: value })
),
{}
)
const onlyDefinedProperties = keepDefinedProperties(data, checkNestedObjects)
await reference.set(onlyDefinedProperties, { ...options })
}
So with my polyfill, you can use ignore the undefined properties even in older firebase versions. In fact it actually might be useful even on newer ones, because it allows you to decide if you want to ignore the undefined properties only at the object root level, or also at potentially nested object properties.
So essentially these two statements are equivalent
await reference.set(data, { ignoreUndefinedProperties: true })
// newer firebase version
await ignoreUndefinedSet(reference, data) // my polyfill
Note, you can also pass other SetOptions or disable the nested objects check
await ignoreUndefinedSet(reference, data, { merge: true }, false)
// does ignoreUndefinedProperties only at root level and uses the merge method

Avoid declaring large object in getDefaultProps prior to an event

The title might not be the best way to describe the problem, but I was wondering if there was a better practice to declaring an object in getDefaultProps?
In my render method I call several keys from from a prop/state that get updated on a click event i.e. this.props.player.name. The problem is that on page load this.props.player is blank and calling .name errors out. I know I can do something like ...
getDefaultProps: function() {
return {
player: {
name: null,
team: null
position: null
}
};
}
but it doesn't feel right. I was hoping there might be something similar to how Ruby does .try() where it won't try to call a method on a undefined prop.
The problem is specifically that this.props.player is undefined, if you define an empty object it will prevent the error from occurring. It's not bad practice to stub out the keys you're anticipating, but setting the default value to {} will be enough to prevent it from throwing.
You can create your data from ImmutableJS. Then you can get any part of your data like this:
this.state.getIn(['player', 'name', ...])
or
this.state.get('player')
This will not throw error even player is not defined or other, it will return undefined (or null) I don't remember
The update and updateIn work the same
see the doc here
I'd like to add this vanilla JS solution too - var x = (user || {}).name;
Source

Passing values back to be rendered by Jade, using Express and Node

I asked a question yesterday, but I've kept going with it. Instead of calling next() and passing an an Error object, I worked out what it was doing, and tried to copy it. Now, when someone logs in and it fails, I do this:
res.render("pages/home",
{
flash:{"danger":["Login failed. Please enter your details and try again."]},
body:{},
section:"home",
locals : { userId : req.body.email }
}
This does exactly the same thing as the old code. I step through it, and I can see that the locals object contains a property called userId, with the value I expect. In the Jade template, I have this:
p it's #{typeof(userId)}
if(typeof(userId) != 'undefined')
p Welcome #{userId}
input(type='text', name='email', id="inputEmail", placeholder="Email", value="#{userId}")
else
input(type='text', name='email', id="inputEmail", placeholder="Email", value="")
This always renders as 'it's undefined' and then an empty text box. I have read several questions on this, and as far as I can see, they all say the same thing: if I set locals to be a JSON object, I can access it's properties by this syntax, but it does not work.
What am I doing wrong ?
You might first need to better understand how locals object actually work.
On the server-side, doing this:
res.render('view', { property: 'value' } );
would make property available in your views like so:
div Value = #{property}
You can also do the following to have the same effect:
res.locals.property = 'value';
res.render('views');
Note the usage of locals object. More info
Coming back to your issue, since you have
res.render("pages/home", { locals: { userId : req.body.email } })
to access userId in this case you would do:
p Welcome #{locals.userId}
So I'm guess you're confusing the two approaches ending up using locals object the wrong way.
OK - turns out that 'locals' doesn't mean anything any more. Leaving my code as it is, I needed to access 'locals.userId', but I could have just set the value of 'userId' and not had the 'locals' object at all.

Vows callback testing in Node.js generating anonymous errors

I am new to the continuation passing style of asynchronous computation used in Node.js applications, and I'm struggling to get a grip on some fairly basic code.
I am trying to write a library which will create an imap interface to imap.gmail.com, and I am trying to follow BDD with 'vows'.js' (with varying levels of success. I'm definitely not following the full red->code->green cycle that I should be, but it's hard to get started in a language that way).
The relevant test case looks like this:
var gmail = require('../lib/gmail.js'),
vows = require('vows'),
assert = require('assert'),
fs = require('fs');
vows.describe('Basic interface tests').addBatch({
'A GMailInterface object can': {
topic: function() {
var gm = Object.create(gmail.GMailInterface);
var settings_file = 'test/test_settings.json';
var settings = JSON.parse(fs.readFileSync(settings_file));
var that = this;
gm.connect(settings.email,settings.password,function() {
that.callback(gm); // ERROR BEING GENERATED HERE
});
},
// ACTUAL VOWS OMITTED - the following is just a test of the callback
'find an email' : {
topic: function(gm) {
console.log(">>>",gm);
},
}
}
}).export(module)
If I write a console.log message right above the line with "ERROR BEING GENERATED HERE", it will print. It will not if I put a message below it. The output of the test gives the following error:
node.js:201
throw e; // process.nextTick error, or 'error' event on first tick
^
Error: Uncaught, unspecified 'error' event.
at EventEmitter.<anonymous> (events.js:50:15)
at EventEmitter.emit (/Users/eblume/Repositories/my_stuff/gmail/node_modules/vows/lib/vows.js:236:24)
at /Users/eblume/Repositories/my_stuff/gmail/node_modules/vows/lib/vows/context.js:31:52
at Object.callback (/Users/eblume/Repositories/my_stuff/gmail/node_modules/vows/lib/vows/context.js:46:29)
at Array.0 (/Users/eblume/Repositories/my_stuff/gmail/test/gmail_test.js:17:14)
at EventEmitter._tickCallback (node.js:192:40)
The code in gmail.js is a bit too much to post here, but here is what I think is the relevant section - I can post more if you ask a question below.
gm.connect = function(username,password,cb) {
var self = this;
self.conn = new ImapConnection({
username: username,
password: password,
host: 'imap.gmail.com',
port: 993,
secure: true
});
async.series([
function(callback){self.conn.connect(callback); },
function(callback){self.conn.openBox('[Gmail]/All Mail',true,callback);}
],
function(err,results) {
if (err) {
die(err);
}
process.nextTick(cb);
});
};
Where might I be going wrong? Thanks for any help!
I recommend reading up on how 'this' works. If nobody's been messing with it the 'that' of that.callback refers to the parent object which is labeled with a literal string as 'A GMailInterface object can' .
I suspect it's the this factor tripping you up. 'callback' should be defined as a method of the same object as the 'topic' method the way you have things set up and that doesn't strike me as the way it's meant to work.
'this' typically refers to nearest ancestor/parent object by default. It ignores wrapping functions unless they're used as constructors using the 'new' keyword in which case it indicates the object instance. In the case of event callbacks in the DOM (browser JS - not node.js which I don't know in-depth as far as events), it typically refers to the object that's had an event triggered on it.
There's no real kludge being fixed with that and self. We just tend to use those to be certain we're addressing the top object in cases where an object has aggregate objects.

Categories

Resources