How to put an object inside another object in firebase database programmatically? - node.js

I'm setting up a firebase database for handling some requests I receive from my application. I need to insert an object "player" inside another object "room" to handle the multiplayer lobbies.
For a better comprehension I also do a little schema using firebase realtime db - schema below.
I want that instead of "room 1" the database puts an unique id, same thing for "player 1" and the other. Do you have any advice to help me doing this?
]

Try this (using a "path" to point to where you need):
const updates = {}
const roomName = 'room1'
const playerName = 'player1'
const path = `chip-chop/rooms/${roomName}/${playerName}`
const playerData = {
name: 'marco',
points: 0,
}
updates[path] = playerData
await firebase.database().ref().update(updates)
Please note that this will update the data. This means that other fields will not be affected (whether that is a bad or good thing is up to you)

Related

Generate mongodb schema without data for a new project installation

I have a project in nodejs with a mongodb database and i would like to create a brand new deployment of the project for another customer so i need the same database structure without old data.
What should i do?
Do i have to create all collections manually or there's a way to script just the database schema?
EDIT
I have been told that mongoose will automatically create the collection if it doesn't exist when the connection to the database is opened
I always prefer using the MVC model.
I will share on of my model file with you to understand how you can write a re-usable database schema code.
const expenseCollection = require('../db').db().collection("expenses");
const ObjectID = require('mongodb').ObjectID
const Money = require('./Money')
let Expense= function(data, userId){
this.data = data,
this.authorId = new ObjectID(userId)
this.errors =[]
}
//MAZZA BELOW ARE THE FIELDS MENTIONED THAT YOU MAY WANT TO HAVE IN YOUR SCHEMA
Expense.prototype.cleanUp = function(){
this.data ={
amount: Number(this.data.amountSpent),
item: this.data.item,
purchaseDate: new Date(this.data.purchaseDate),
notes: this.data.notes,
status: false,
expType: this.data.expType,
authorId : this.authorId,
}
}
module.exports = Expense
Also, I will share with you how can you pass the data in this constructor.
(From one of the of the controller file)
const moneyController = require('./moneyController')
const Expense = require('../models/Expense')
exports.saveExpense = async function(req, res){
let data = {
balance:0,
}
let money = new Money(data)
await money.addMoney(req.session.user_id)
await money.subtractBal(req.body.amountSpent, req.session.user._id )
//MAZZA FOCUS ON THE BELOW LINE:
let expense = new Expense(req.body, req.session.user._id)
await expense.saveExpense()
res.redirect('/')
}
Primarily, the model will be a file, wherein you can write the reusable schema script.
Consider making separate model for each collection, that is 1 collection = 1 new model in your code.
Also, in the above code that I shared with you, it will automatically create the collection even if it does not already exist in the mongodb.

Dynamic Mongoose Field Type

I am developing an app where a user could store his model on a database using mongoDB and mongoose. Taken from mongoose tutorial the type of the field has to be defined. For example here we have to define that the name is a string.
const personSchema = new mongoose.Schema({
name: String
});
const Person = mongoose.model('Person', personSchema);
Is there any way to make it dynamic to user's input. I want to create a form where a user will enter a field name and select one of the field types that Mongoose offers [String,Number,Date etc], but I cannot figure any way to implement it. To be honest I don't know even if this is a good approach. An alternative would be to pass everything as a String and serialise the input in order to store it. I want to achieve something like that:
const {fieldName,fieldType} = userInput;
const customSchema = new mongoose.Schema({
fieldName: fieldType
});
const CustomModel = mongoose.model('CustomSchema', customSchema);
Is this possible or should I implement another approach? An alternative would be to pass everything as a String and serialise the input in order to store it.
Thank you in advance!
If I understand you correctly it should work like that:
User defines the model to store
Schema is created using the data provided by the user
User can pass the data to store using the previously created model which will validate the user's input later
In fact, I'm working on a project that has the same functionality. Here is how we did it.
A user sends the model and we store it as a string since we need to have the ability to create the model once again.
When the user passes new data to store using the created model we get the string from mongo and parse it to create the schema. This operation is relatively easy (but depends on what you want to achieve as it can get tricky if you want to have some advanced validation) as you have to just create an object with correct values from mongoose. Something like this for every field that the user has defined.
export const fieldConverter = ({name, type}) => {
switch (type) {
case 'String':
return { [name]: String };
case 'Number':
return { [name]: Number };
...
}
When you have your object ready then you can create a model out of it.
The line with accessing your model from mongoose.models is important as the mongoose will cache the model and throw an error if you try to create it once again.
const DatasetModel =
mongoose.models["your-model-name"] ??
mongoose.model("your-model-name", new mongoose.Schema(schema));
Now when you have the model the rest is just like with the normally created one.
This approach worked for us so I'm adding this as inspiration maybe it will help you. If you have any specific questions about the implementation feel free to ask I will be happy to help.
There is also a Mixed type in mongoose if you don't need the validation later. You can check it here: https://mongoosejs.com/docs/schematypes.html#mixed
You can use Schema.Types.Mixed, An "anything goes" SchemaType. Mongoose will not do any casting on mixed paths.
let customSchema = new Schema({custom: Schema.Types.Mixed})
Read more about it here
After some research I figure at that mongoose type can also be strings. For example
const personSchema = new mongoose.Schema({
name: "String"
});
const Person = mongoose.model('Person', personSchema);
Mongoose will handle it

Update multiple dynamic nested Firestore fields using FieldPath

I am trying to update some Firestore documents in a batch in Nodejs. Some of the fields I'm updating are nested Map fields with periods in their names that are generated dynamically. I understand this has been covered before and the solution is:
var email = 'test#email.com';
var myPath = new admin.firestore.FieldPath('email', email);
batch.update(db.collection('collection').doc('document'), myPath, admin.firestore.FieldValue.delete());
This would delete the field "email.'test#email.com'". However, I'm trying to update multiple fields like this:
var email = 'test#email.com';
var myPath = new admin.firestore.FieldPath('email', email);
var updateObject = {[myPath]: admin.firestore.FieldValue.delete()};
updateObject = {...updateObject, count: admin.firestore.FieldValue.increment(1)};
batch.update(db.collection('collection').doc('document'), updateObject);
When I try this, the count field is updated, but the nested email field is unchanged. I'm assuming there is some issue with how I'm getting the FieldPath object in there. All the examples I can find only show updating one field at a time. There are also cases where I'll need to update multiple nested fields (such as two fields in the email map). How should this be done correctly?
I just ran this tiny test on a database on my own:
const docRef = admin.firestore().doc("68821373/i6ESA7AZwZRhPsdDHGmY");
const updates = {
toDelete: admin.firestore.FieldValue.delete(),
toIncrement: admin.firestore.FieldValue.increment(1)
};
docRef.update(updates);
This incremented the toIncrement field and removed the toDelete field. So the operations can be combined in a single call, although I am not sure how your code is different.
I also quickly ran a test with a batch, just in case that makes a difference:
const docRef = admin.firestore().doc("68821373/i6ESA7AZwZRhPsdDHGmY");
const batch = admin.firestore().batch();
const updates = {
toDelete: admin.firestore.FieldValue.delete(),
toIncrement: admin.firestore.FieldValue.increment(1)
};
batch.update(docRef, updates);
await batch.commit();
But here too, the increment and delete are both executed without problems for me.

How to create a new Mongoose document, and then update before saving

I'm trying to create a new Mongoose document first
let newTrade = new TradeModel({
userId: userId,
symbol: symbol
})
Then I need to send this item to another server, to get the other details
let orderReceived = await sendOrderToServer(newTrade);
And then I want to merger this in with the new document and save
newTrade = {...newTrade, ...orderReceived}
But once I alter the original document, I loose access to the .save() method. I cant run .save() first becasue its missing required fields. I really just need the Trade._id first before sending to the other server which is why I'm doing it this way. Any suggestions? thanks.
You can use the mongoose.Types.ObjectId() constructor to create an id and then send that to your server, when the response comes back, create a document based on that.
EDIT: Adding few examples for clarity
let newTradeId = new mongoose.Types.ObjectId(); // With "new" or without, Javascript lets you use object constructors without instantiating
let orderReceived = await sendOrderToServer(newTradeId);
let newTrade = new TradeModel({ ...orderReceived }); // Create the newTrade by destructuring the order received.
// TADA! We are done!

Mongoose key/val set on instance not show in JSON or Console.. why?

I have some information on my mongoose models which is transient. For performance reasons I dont wish to store it against the model.. But I do want to be able to provide this information to clients that connect to my server and ask for it.
Here's a simple example:
var mongoose = require('mongoose'),
db = require('./dbconn').dbconn;
var PersonSchema = new mongoose.Schema({
name : String,
age : Number,
});
var Person = db.model('Person', PersonSchema);
var fred = new Person({ name: 'fred', age: 100 });
The Person schema has two attributes that I want to store (name, and age).. This works.. and we see in the console:
console.log(fred);
{ name: 'fred', age: 100, _id: 509edc9d8aafee8672000001 }
I do however have one attribute ("status") that rapidly changes and I dont want to store this in the database.. but I do want to track it dynamically and provide it to clients so I add it onto the instance as a key/val pair.
fred.status = "alive";
If we look at fred in the console again after adding the "alive" key/val pair we again see fred, but his status isnt shown:
{ name: 'fred', age: 100, _id: 509edc9d8aafee8672000001 }
Yet the key/val pair is definitely there.. we see that:
console.log(fred.status);
renders:
alive
The same is true of the JSON representation of the object that I'm sending to clients.. the "status" isnt included..
I dont understand why.. can anyone help?
Or, alternatively, is there a better approach for adding attributes to mongoose schemas that aren't persisted to the database?
Adding the following to your schema should do what you want:
PersonSchema.virtual('status').get(function() {
return this._status;
});
PersonSchema.virtual('status').set(function(status) {
return this._status = status;
});
PersonSchema.set('toObject', {
getters: true
});
This adds the virtual attribute status - it will not be persisted because it's a virtual. The last part is needed to make your console log output correctly. From the docs:
To have all virtuals show up in your console.log output, set the
toObject option to { getters: true }
Also note that you need to use an internal property name other than status (here I used _status). If you use the same name, you will enter an infinite recursive loop when executing a get.
Simply call .toObject() on the data object.
For you code will be like:
fred.toObject()
This has been very helpful. I had to struggle with this myself.
In my case, I was getting a document from mongoose. When I added a new key, the key was not visible to the object if I console.log it. When I searched for the key (console.log(data.status), I could see it in the log but not visible if I logged the entire object.
After reading this response thread, it worked.
For example, I got an object like this one from my MongoDB call:
`Model.find({}).then(result=> {
//console.log(result);// [{ name:'John Doe', email:'john#john.com'}];
//To add another key to the result, I had to change that result like this:
var d = result[0];
var newData = d.toJSON();
newData["status"] = "alive";
console.log(newData);// { name:'John Doe', email:'john#john.com', status:'alive'};
}).catch(err=>console.log(err))`
Hope this helps someone else.
HappyCoding

Resources