I have this object called 'ctx.instance' which has the following properties:
firstName: 'Ron',
lastName: 'Santo',
minor: true,
accepted: false,
emailChanged: false,
organizationId: 000000000000000001000001,
isDeleted: false,
userId: 55e17a46e410f9603cea515b
This object was passed into my function. I need to strip off the 'emailChanged' property before saving it to the database. So I did this:
delete ctx.instance.emailChanged;
The delete returns 'true' which means the property doesn't exist.
The following statement after the delete yields false which also means it should be gone:
'emailChanged' in ctx.instance
Yet, if I do a console.log(ctx.instance), the 'emailChanged' property is still there and it gets saved to the database.
If I check the property's properties, it says it is configurable. If I do a console.log(ctx.instance.emailChanged) after the delete statement, it says 'undefined'.
Why is it still there?
I've searched all over the internet, tried tons of different things, and I can't find why this is occurring. This is happening within a Node environment.
UPDATE:
The DB is Mongo. I'm using Loopback.js models and framework.
The data variable is the object submitted to the server from the client via a PUT. The data object was originally JSON but Loopback has made it a JavaScript object.
The code is within an operation hook so the save to the DB doesn't live within this function.
The 'delete' statement is the last statement within the function before I pass it back off to the framework.
Here is the minimum code for the hook:
module.exports = function( Member )
{
Member.observe( 'before save', upsertMember );
function upsertMember( ctx, next )
{
// displays 'true'
console.log( ctx.instance.hasOwnProperty( 'emailChanged' ) );
// displays 'false'
console.log( ctx.instance.emailChanged );
var isDeleted = delete ctx.instance.emailChanged;
// displays 'true'
console.log( isDeleted );
// displays 'false'
console.log( 'emailChanged' in ctx.instance );
// displays 'false'
console.log( ctx.instance.hasOwnProperty( 'emailChanged' ) );
// displays 'undefined'
console.log( ctx.instance.emailChanged );
// displays object properties including 'emailChanged'
console.log( ctx.instance );
// pass control back to loopback for upsert
// 'emailChanged' gets into MongoDB record
next();
}
}
If any of you know a JSFiddle type of environment that includes Loopback, I'll throw it in there.
Screenshot of my debugger watch right after the delete statement:
According to Loopback Operation Hooks
Removing unneeded properties
To remove unwanted properties (fields) from the context object, use the following:
ctx.instance.unsetAttribute('unwantedField');
This completely removes the field and prevents inserting spurious data into the database.
Should have done this:
ctx.instance.unsetAttribute('emailChanged');
Could it be due to the PUT verb which is used to replace the whole instance and not only some attributes ?(To change only certain attributes you should use the PATCH verb).
So it might be loopback filling the missing attribute in the model and providing a default boolean value (false) for the "emailChanged" attribute.
I was able to remove the unnecessary property from being saved to dataabse using follwing statement Loopback version 3.
In case of ctx.data
delete ctx.data['propertyToBeRemoved'];
In case of ctx.instance
ctx.instance.unsetAttribute('propertyToBeRemoved')
Hope this works
data.emailChanged = undefined;
data.save(callback);
The questioner wants to delete the property of a model and save it to database. But the property of a model cannot be deleted using delete.
Related
Requirements
The object has two properties:
A number, let's call it firstField
A string, let's call it secondField
Now here are the validation requirements
firstField is always required
secondField is required when firstField is equals 1. And forbidden when firstField is not 1
That could easily get solved with a .when()
But actually to find out if the secondField is required, I have to do some checks based on the firstField and a variable in the validation context.
Current code
Joi.object({
firstField: Joi.number().required(),
secondField: Joi.custom((value, helpers) => {
if (firstField === helpers.prefs.context.someValue) {
// test secondField with forbidden and return error when secondField is provided, otherwise return value
}
// test secondField with required and return error when secondField is not provided, otherwise return value
}),
});
Now my problem is, that the custom validator does not get executed when I send in:
{
firstField: "test",
}
But I want it to execute
What I tried
When I provide secondField in the input, the custom validator does get executed
When I use a .when() instead of the custom validator, then the .when() does get executed, even when secondField is not provided
So i tried to put a .when("firstField", { is: Joi.any(), then: Joi.any(), otherwise: Joi.any()}) in front of the custom validator, but still no luck
I really tried around a lot
A last solution I see is to put the custom validator on the parent object of the fields, and in that test the child fields in context of the validation context
What is the best way to solve this? Is it even recommended testing for forbidden/required on the field directly, or should that be done on the parent all the time?
You can use .ref() to access context values, per the docs:
ref(key, [options])
If a key starts with $ is signifies a context reference which is looked up in the context option object.
Source: https://joi.dev/api/?v=17.7.0#refkey-options
With this you can leverage when() to express your condition:
Joi.when("firstField", {
is: Joi.ref("$someValue"),
then: Joi.forbidden(),
otherwise: Joi.string(),
});
I'm trying to implement an AVA Unit Test for my mixpanel implementation. To do this, I'm comparing the result of mixpanel.track() where if it returns anything, the track was successful, otherwise, it should be undefined.
I thought maybe it was that it was using a different mixpanel instance so I tried creating a named instance and ensuring that but it was to no avail. I'm also trying the same process but with Amplitude and it seems to be working fine (when I am opted out, the response fails as expected)
I have done this in my components where if
const test = mixpanel.track('event_name', {}) is successful, !!test === true but if I do mixpanel.opt_out.tracking() prior to const test = mixpanel.track('event_name', {}), then !!test === undefined.
Expected behaviour (and the observed behaviour when I use it in my components):
trackResponse === undefined
Observed behaviour:
trackResponse === { event: 'asdf',
properties:
{ '$browser': 'Safari',
'$current_url': 'about:blank',
'$browser_version': null,
'$screen_height': 0,
'$screen_width': 0,
mp_lib: 'web',
'$lib_version': '2.30.1',
time: 1572898982.142,
distinct_id: '[some_id]',
'$device_id': '[some_id]',
'$initial_referrer': '$direct',
'$initial_referring_domain': '$direct',
token: '[token]' } }
where [some_id] and [token] are some distinct values I've deleted.
I don't understand why in the AVA test, I'm receiving a response when normally a failed track() results in an undefined response. Could someone shine some light on this?
Let me know if I need to provide any additional information. Thanks.
I figured it out in case anyone else runs into this issue.
I used a debugger to step into the mixpanel.track() calls and figured out that to see if the user had opted out, mixpanel checks for a property in the localStorage and compares it to see if it's === to '0'. If this fails, it assumes the user has not opted out and carries out the track call as normal.
I guess during the AVA test, it was unable to access this property and assumed the user had not opted out. To fix it, in my call to mixpanel.init(), I added opt_out_tracking_persistence_type: 'cookie' as an option so that my opt_out call was being saved somewhere that the property could be accessed during the test.
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
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
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.