Check if field exists, delete it and create a new one - node.js

I am trying to write a NodeJS program that on a given document, it checks if a field exists from a given list of fields - lets say the key we are looking for is key1. If the field exists it is deleted and a new field is added with the field incremented - key2 with some value.
// Get the `FieldValue` object
let FieldValue = require('firebase-admin').firestore.FieldValue;
// Create a document reference
let cityRef = db.collection('cities').doc('BJ');
// Remove the 'capital' field from the document
let removeCapital = cityRef.update({
capital: FieldValue.delete()
});
From the documentation I found the way to delete a field but I am not sure how to check if the key exists so the program knows what key to create after the deletion.
For the program the key might be any sequence of letters followed by a sequence of numbers - key1, key2, key3 etc, so I need a way to know which of those exists in order to correctly delete and then increment the new one

To know the list of fields for a document you need to fetch it with the get() method, see https://firebase.google.com/docs/firestore/query-data/get-data and https://firebase.google.com/docs/reference/js/firebase.firestore.DocumentReference#get
For example:
let cityRef = db.collection('cities').doc('BJ');
cityRef.get().then(function(doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch(function(error) {
console.log("Error getting document:", error);
});
In the code above, doc is a DocumentSnapshot and if you call the data() method it will return "all fields in the document as an Object."
You just have to loop over the Object returned by the data() method to get the value X of the key(X) field, then increment it and write a new field key(X+1) with, for example, the update() method.
Note that, depending on your exact requirement, you may have to use a Transaction.

Related

How to fill a reference field automatically in Wix (Velo), with or without code?

I am using a reference field, which contains users nickname, to make a connection between my main collection and 'public data' collection created as default by Wix. I created this reference field to populate a repeater from the two 'main' and 'public data' collections. Is it possible to automatically fill a reference field without using code? If not, then how can use 'beforeInsert' hook to fill the 'the reference' field using code.
I tried to do so in the backend by this code, but it doesn't work.
import { currentMember } from 'wix-members-backend';
export function Collection1_beforeInsert(item, context) {
currentMember.getMember()
.then((member) => {
const id = member._id;
const fullName = `${member.contactDetails.firstName} ${member.contactDetails.lastName}`;
const nickname = member.profile.nickname
console.log(nickname)
item.referenceField= "nickname"
// return member;
})
.catch((error) => {
console.error(error);
});
return item;
}
First off, I'm assuming you're using a regular reference field and not a multi reference field.
Second, it's important to understand how reference fields work before continuing. The reference field holds the ID of the item it is referencing. So when you say the reference field contains a user's nickname, that can't be true. It can contain the ID of the item in PrivateMembersData collection with the nickname you want.
Third, as just mentioned, the nickname field does not exist in the PublicData collection. It is part of the PrivateMembersData collection.
So, if you want to connect your collection to another with the nickname field, you need to set your reference field to reference the PrivateMembersData collection and then store the proper ID in that field.
(Side point: In your code you are putting the same string, "nickname", in every reference field value. You probably meant to use nickname without the quotes. You're also not using promises correctly.)
You can try this code. It should get you closer to what you're looking for.
import { currentMember } from 'wix-members-backend';
export async function Collection1_beforeInsert(item, context) {
const member = await currentMember.getMember();
item.referenceField = member._id;
return item;
}

sails beforeUpdate doesn't contains the object Id

I need to add some condition to beforeUpdate, sending to a specific event only if certain property was updated, and need to know its previous value.
I need the object Id to do that, but it is not appears in beforeUpdate values. How can I know the object id?
beforeUpdate(values, cb) {
// if another field was updated except of the lastActive field
if (values.length > 1) {
const id = this.update.arguments[0];
// id is undefined now. how can I get user id?
const oldUser = User.findOne({id: values.id});
// because I need to know user old values in sendUser function
myFunction.sendUser(oldUser);
}
cb();
}

Why can't I seem to merge a normal Object into a Mongo Document?

I have a data feed from a 3rd party server that I am pulling in and converting to JSON. The data feed will never have my mongoDB's auto-generated _ids in it, but there is a unique identifier called vehicle_id.
The function below is what is handling taking the data-feed generated json object fresh_event_obj and copying its values into a mongo document if there is a mongo document with the same vehicle_id.
function update_vehicle(fresh_event_obj) {
console.log("Updating Vehicle " + fresh_event_obj.vehicleID + "...");
Vehicle.find({ vehicleID: fresh_event_obj.vehicleID }, function (err, event_obj) {
if (err) {
handle_error(err);
} else {
var updated = _.merge(event_obj[0], fresh_event_obj);
updated.save(function (err) {
if (err) {
handle_error(err)
} else {
console.log("Vehicle Updated");
}
});
}
});
}
The structures of event_obj[0] and fresh_event_obj are identical, except that event_obj[0] has _id and __v while the "normal" object doesn't.
When I run _.merge on these two, or even my own recursive function that just copies values from the latter to the former, nothing in the updated object is different from the event_obj[0], despite fresh_event_obj having all new values.
Does anyone have any idea what I'm doing wrong? I feel it is obvious and I'm just failing to see it.
The problem is that if you don't have properties defined in your schema, and if they don't already exist, you can't create them with
doc.prop = value
even if you have {strict:false} in your schema.
The only way to set new properties is to do
doc.set('prop', value)
(You still have to have {strict:false} in your schema if that property doesn't exist in your schema)
As for having too many properties to be defined in schema, you can always use for-in loop to go through object properties
for(key in fresh_event_obj)
event_obj.set(key, fresh_event_obj[key]);

Mongoose.js conditional populate

I'm working with some old data where some of the schema has a "mixed" type.
Basically sometimes a value will be a referenced ObjectID, but other times it'll be some text (super poor design).
I unable to correctly populate this data because of the times a non-ObjectID appears.
So, for my actual question: Is it possible to create a populate (on a collection) that is conditional; I need to be able to tell the populate to skip those other values.
Yes, you can do that check the middleware function on the Mongoose API reference
http://mongoosejs.com/docs/middleware.html
What you need to do is before you populate those data, you validate the data if is is Object ID or not, if it is Object ID, you call next() to pass the next function, else you just return, this will skip it
Example
xSchema.pre('validate', function(next){
var x = this;
var checkXType = typeof x.id;
if (checkXType === String) {
return;
} else {
next();
}
});

nodejs: save function in for loop, async troubles

NodeJS + Express, MongoDB + Mongoose
I have a JSON feed where each record has a set of "venue" attributes (things like "venue name" "venue location" "venue phone" etc). I want to create a collection of all venues in the feed -- one instance of each venue, no dupes.
I loop through the JSON and test whether the venue exists in my venue collection. If it doesn't, save it.
jsonObj.events.forEach(function(element, index, array){
Venue.findOne({'name': element.vname}, function(err,doc){
if(doc == null){
var instance = new Venue();
instance.name = element.vname;
instance.location = element.location;
instance.phone = element.vphone;
instance.save();
}
}
}
Desired: A list of all venues (no dupes).
Result: Plenty of dupes in the venue collection.
Basically, the loop created a new Venue record for every record in the JSON feed.
I'm learning Node and its async qualities, so I believe the for loop finishes before even the first save() function finishes -- so the if statement is always checking against an empty collection. Console.logging backs this claim up.
I'm not sure how to rework this so that it performs the desired task. I've tried caolan's async module but I can't get it to help. There's a good chance I'm using incorrectly.
Thanks so much for pointing me in the right direction -- I've searched to no avail. If the async module is the right answer, I'd love your help with how to implement it in this specific case.
Thanks again!
Why not go the other way with it? You didn't say what your persistence layer is, but it looks like mongoose or possibly FastLegS. In either case, you can create a Unique Index on your Name field. Then, you can just try to save anything, and handle the error if it's a unique index violation.
Whatever you do, you must do as #Paul suggests and make a unique index in the database. That's the only way to ensure uniqueness.
But the main problem with your code is that in the instance.save() call, you need a callback that triggers the next iteration, otherwise the database will not have had time to save the new record. It's a race condition. You can solve that problem with caolan's forEachSeries function.
Alternatively, you could get an array of records already in the Venue collection that match an item in your JSON object, then filter the matches out of the object, then iteratively add each item left in the filtered JSON object. This will minimize the number of database operations by not trying to create duplicates in the first place.
Venue.find({'name': { $in: jsonObj.events.map(function(event){ return event.vname; }) }}, function (err, docs){
var existingVnames = docs.map(function(doc){ return doc.name; });
var filteredEvents = jsonObj.events.filter(function(event){
return existingVnames.indexOf(event.vname) === -1;
});
filteredEvents.forEach(function(event){
var venue = new Venue();
venue.name = event.vname;
venue.location = event.location;
venue.phone = event.vphone;
venue.save(function (err){
// Optionally, do some logging here, perhaps.
if (err) return console.error('Something went wrong!');
else return console.log('Successfully created new venue %s', venue.name);
});
});
});

Resources