Firebase push - deletes unique object and inserts new one (Overwrites content basically) - javascript

I have an application that is running with firebase. When I try to use the push() method, it basically overwrites the existing JSON. Here's an example:
First time around, the following JSON is generated:
JSON
"deviceIDs" : {
"-JzCx5C_13eoXPEgklMW" : {
"author" : "gracehop22",
"deviceID" : "99alpha",
"title" : "Announcing COBOL, a New Programming Language"
}
}
Next time around, if I call the same function, the above JSON is deleted and a new JSON is inserted such as this one:
JSON
"deviceIDs" : {
"-JzCxbuEj2V1kmvvgqnc" : {
"author" : "gracehop22",
"deviceID" : "99alpha",
"title" : "Announcing COBOL, a New Programming Language"
}
}
Here's my code snippet:
function CreateUserProfile(UID, name, email, deviceID) {
var ref = new Firebase($scope.firebaseurl + '/' + UID);
var profileArray = {UserProfile:{}};
profileArray.UserProfile.UID = UID;
profileArray.UserProfile.name = name;
profileArray.UserProfile.email = email;
profileArray.UserProfile.deviceID = deviceID;
var onComplete = function (error) {
if (error) {
console.log('Synchronization failed');
} else {
//1. On Success, Store Key User Profile Elements
localStorage.setItem("kasimaProfileInfo",JSON.stringify(profileArray));
$rootScope.username = name;
//2. Hide the feedback and change screens
$timeout(function () {
$scope.HideFeedback();
$scope.ChangeLoc('/featured');
}, 1500);
}
};
ref.set(profileArray, onComplete);
var postsRef = ref.child("deviceIDs");
var newPostRef = postsRef.push();
newPostRef.set({
deviceID: deviceID,
author: "gracehop22",
title: "Announcing COBOL, a New Programming Language"
});
}

You're overwriting the entire ref when you're setting profileArray:
...
ref.set(profileArray, onComplete);
var postsRef = ref.child("deviceIDs");
...
You'll probably want to use update() there:
...
ref.update(profileArray, onComplete);
var postsRef = ref.child("deviceIDs");
...
Update
The Firebase update() functions set the value of the properties in the JSON object you pass it. So your new profileArray.UserProfile will replace the existing data.
The solution is to not build a nested JSON structure locally, but instead update the data at the lower location where it needs updating:
ref.child('UserProfile').update(profileArray.UserProfile, onComplete);
This removes the entire need for the profileArray:
var userProfile = {
UID: UID,
name: name,
email: email,
decideID: deviceID
};
ref.child('UserProfile').update(userProfile, onComplete);
For a working example, see: http://jsbin.com/ciqoge/edit?js,console
For a next time: if you provide such a jsbin/jsfiddle straight away, it will be much easier to quickly help you.

Related

Dynamic key name for mongodb Realm

I'm making a digital Christmas Kalendar for a friend. Every day he can claim a Steam game.
So in mongodb in the user account that I made for him there is a key called codes (object). The structure is as follows:
_id: blbalbalba
codes: {
1 : {
title: GTA V,
code: AISHD-SDAH-HAUd,
claimed_at: ,
},
2 : {
title: Fortnite,
code: HHF7-d88a-88fa,
claimed_at: ,
}
}
Just example data. Now in the client app, when button (door) 7 is pressed/opened I want to insert the current date to the key "claimed_at" in the object with key name "7".
I tried some variations of:
const result = await PrivateUserData.updateOne(
{ id: myID },
{ $set: { "codes.`${door_number}`.date_claimed" : date,
}
}
);
But this doesn't work. What did work was: "codes.5.date_claimed". So a static path. In that case the object with name 5 got it's date_claimed key updated.
But how do I use a dynamic path with a variable instead of a number?
Thanks in advance!
If you know the variable value before calling the query i think both of the bellow can work.
var door_number=5;
var date= new Date("...");
const result = await PrivateUserData.updateOne(
{ id: myID },
{ $set : { ["codes."+door_number+".date_claimed"] : date}}
);
var door_number=5;
var date= new Date("...");
const result = await PrivateUserData.updateOne(
{ id: myID },
{ $set : { [`codes.${door_number}.date_claimed`] : date}}
);
If the dynamic behaviour is based on information on the database, send if you can sample data and expected output, so we know what you need.

how to save objects from a JSON in different documents with mongoose?

Well I have the following doubt. I have the following:
JSON
[
{"name": "juan", "age": 10}
{"name": "pedro", "age": 15}
{"name": "diego", "age": 9}
]
User Schema
_group:{
type: mongoose.Schema.Types.ObjectId,
ref: 'Group'
},
name: {
type: String
},
age: {
type: Number
}
And I need to save or update this data in different docs with nodejs/mongoose. I planned to do the following
var data = JSON.parse(json)
for (var i = data.length - 1; i >= 0; i--) {
var name = data[i].name;
var age = data[i].age;
User.find({'name': par, '_group': group_id}, (err, user)=>{
if(err)
next(err);
// if it does not exist, create new doc
if(_.isEmpty(doc)){
var newuser = new User;
newuser.name = name;
newuser.age = age;
newuser.save((err, saved)=>{
})
}// if it exists, update it
else if(!_.isEmpty(doc)){
user.age = age;
user.save((err, saved)=>{
})
}
})
}
as you will see, the variables age and name within User.find remain undefined, so this does not work for me.
First of all, is it the right way to save this data? If so, how could I can use the for cycle variables (name and age) within User.find? If not, what do you recommend me to do?
Thanks,
Eduardo
NodeJS, ExpressJS, Mongoose
There is one more issue which I think you are facing that you are calling a method inside a loop and it takes a call-back, so it doesn't wait here for coming back and move to second iteration, so you might face undefined and some un-expected behavior.
I suggest you should use async/await
let user = await User.findOneAndUpdate({'name': par, '_group': group_id}, { name, age }, { upsert: true })
If you parsed given JSON well and assigned values to name and age, they are not undefined within User.find scope.
Did you checked those variables?
var name = data[i].name;
var age = data[i].age;
You can use mongoose findOneAndUpdate with the option { upsert: true }.
This tries to update an object in the DB and, if the object is not found, it creates it. So:
for (var i = data.length - 1; i >= 0; i--) {
var name = data[i].name;
var age = data[i].age;
User.findOneAndUpdate({'name': par, '_group': group_id}, { name, age }, { upsert: true, new: true, lean: true }, (err, updated) => {
if(err) console.log(err);
else console.log(updated);
})
}
The option new tells to return the updated object and the option lean tells to return a plain JSON, instead of Mongoose document object (the same as calling doc.toJson())
Using the upsert option, you can use findOneAndUpdate() as a find-and-upsert operation. An upsert behaves like a normal findOneAndUpdate() if it finds a document that matches filter. But, if no document matches filter, MongoDB will insert one by combining filter and update as shown below.
data.forEach( user => {
User.findOneAndUpdate({'name': user.name , '_group': group_id}, user , {upsert: true, new: true}, (err, data) => {
if(err) console.log(err);
console.log(data);
})
})

How to clone a node to another path based on a reference value from the initial path on Google Cloud Functions?

I am trying clone an "original" node's data (as soon as I create the data) to a path that is based on the original node's path.
This is my data structure:
root: {
doors: {
111111111111: {
MACaddress: "111111111111",
inRoom: "-LBMH_8KHf_N9CvLqhzU", // I will need this value for the clone's path
ins: {
// I am creating several "key: pair"s here, something like:
1525104151100: true,
1525104151183: true,
}
}
},
rooms: {
-LBMH_8KHf_N9CvLqhzU: {
ins: {
// I want the function to clone the same data here:
1525104151100: true,
1525104151183: true,
}
}
}
My cloud function is now like this:
exports.updateRoom = functions.database.ref('/doors/{MACaddress}/ins').onWrite((change, context) => {
const beforeData = change.before.val(); // data before the write
const afterData = change.after.val(); // data after the write
const roomPushKey = change.before.ref.parent.child('/inRoom');
console.log(roomPushKey); // this is retrieving all the info about the ref "inRoom" but not its value...
Question 1) How can I get to this ref/node's value?
My code goes on by trying to get the value like this.
roomPushKey.once('child_added').then(function(dataSnapshot) {
let snapVal = dataSnapshot.val();
console.log(snapVal);
});
Question 2 (which I think is basically question 1 rephrased): How can I get the snapVal outside the then. method's scope?
return change.after.ref.parent.parent.parent.child('/rooms')
.child(snapVal).child('/ins').set(afterData);
// snapVal should go above
});
Error message: ReferenceError: snapVal is not defined
The following should work.
const admin = require("firebase-admin");
....
....
exports.updateRoom = functions.database.ref('/doors/{MACaddress}').onWrite((change, context) => {
const afterData = change.after.val(); // data after the write
const roomPushKey = afterData.inRoom;
const ins = afterData.ins;
const updates = {};
updates['/rooms/' + roomPushKey] = ins;
return admin.database().ref().update(updates);
}).catch(error => {
console.log(error);
//+ other rerror treatment if necessary
});
Here are some explanations:
You get the roomPushKey by reading the "data after the write" as an object: roomPushKey = afterData.inRoom. You don't need to do roomPushKey.once('child_added').then()
Once you have the roomPushKey, you create a new child node in the rooms node by using update() and creating an object with square brackets notation which allow you to assign the id of the node (i.e. roomPushKey).
Note that you could also do:
return admin.database().ref('/rooms/' + roomPushKey).set(ins);
Note also that you have to import firebase-admin in order to be able to do return admin.database().ref()...
Finally, I would suggest that you have a look at the three following videos from the Firebase team: youtube.com/watch?v=7IkUgCLr5oA&t=517s & youtube.com/watch?v=652XeeKNHSk&t=27s & youtube.com/watch?v=d9GrysWH1Lc. A must for anyone starting coding for Cloud Functions.

Meteor Collection.insert() not returning Id

I'm trying to get the Id of the new insert so I can push the Id onto another collection.
According to this post =>
Meteor collection.insert callback to return new id and this post => Meteor collection.insert callback issues, I should be able to
return Collection.insert(obj);
and it will return the ID of the newly inserted data to my client.
Instead I'm getting an Observable like this:
{_isScalar: false}
_isScalar: false
__proto__:
constructor: f Object()
hasOwnProperty: f hasOwnProperty()
//many other object properties
The documentation seems pretty clear that I should be getting the ID in return. Is this a bug? https://docs.meteor.com/api/collections.html#Mongo-Collection-insert]
My version of meteor is 1.4.4.5...
I've been working on this issue for a few days and I've tried getting the ID many different ways, nothing I've tried results in the ID.
Here's my full code for reference:
Server:
submitStuff: function(data): string{
var loggedInUser = Meteor.user();
if (!loggedInUser || !Roles.userIsInRole(loggedInUser,['user','admin'])){
throw new Meteor.Error("M504", "Access denied");
} else {
try{
let insertedData = null;
const model: MyModel[] = [{
data.stuff //bunch of data being inserted
}];
model.forEach((obj: MyModel) => {
insertedData = Collection.insert(obj);
});
return insertedData;
} catch(e) {
throw new Meteor.Error(e + e.reason, "|Throw new error|");
}
}
},
Client:
Meteor.call('submitData', data, (error, value) => {
if(error){
this.errors.push(error + error.reason + "|new Error code|");
}
else{
Meteor.call('userPushId', value, (error) => { //this pushes Id onto the second collection
if(error){
this.errors.push(error + error.reason + "|new Error code|");
}
});
}
Server
// ...
try {
const myModels = [{ ...whatever }];
// I'm using map so I return an array of id's.
//your forEach about technically should end up with only one id,
// which is the last insertion
const insertedDataIds = myModels.map(model => Collection.insert(model));
// you can also write to the secondary location here with something like:
const secondaryWrite = SecondaryCollection.insert({ something: insertedDataIds });
return insertedDataId's
}
//...
Client
also I don't know if this is just a typo on stack but your Meteor.call('submitData') should be Meteor.call('submitStuff') but that is probably not your actual issue.
Alright, so after searching around I realized I'm using angular-meteors rxjs package to create a MongoObservable when creating a collection instead of Mongo's regular collection.
Because I'm using MongoObservable and trying to invoke .insert() on that, the return is a bit different then a regular Mongo Collection and will return an Observable instead of the Id. Documentation Here
If you add a .collection after the collection name you will get the Id in return like so:
submitStuff: function(data): string{
var loggedInUser = Meteor.user();
if (!loggedInUser || !Roles.userIsInRole(loggedInUser,['user','admin'])){
throw new Meteor.Error("M504", "Access denied");
} else {
try{
let id = new Mongo.ObjectID();
const model: MyModel[] = [{
data.stuff //bunch of data being inserted
_id:
}];
model.forEach((obj: MyModel) => {
insertedData = Collection.collection.insert(obj); //added .collection
});
return insertedData;
} catch(e) {
throw new Meteor.Error(e + e.reason, "|Throw new error|");
}
}
},

Meteor: How to publish cursor that is depending on cursor of other collection

I'm having the following data structure in my Meteor project:
- Users with a set of list-ids that belong to the user (author)
- Lists that actually contain all the data of the list
Now I'm trying to publish all Lists of a user to the client. Here is a simple example:
if (Meteor.isClient) {
Lists = new Meteor.Collection("lists");
Deps.autorun(function() {
Meteor.subscribe("lists");
});
Template.hello.greeting = function () {
return "Test";
};
Template.hello.events({
'click input' : function () {
if (typeof console !== 'undefined')
console.log(Lists.find());
}
});
}
if (Meteor.isServer) {
Lists = new Meteor.Collection("lists");
Meteor.startup(function () {
if ( Meteor.users.find().count() === 0 ) {
Accounts.createUser({ //create new user
username: 'test',
email: 'test#test.com',
password: 'test'
});
//add list to Lists and id of the list to user
var user = Meteor.users.findOne({'emails.address' : 'test#test.com', username : 'test'});
var listid = new Meteor.Collection.ObjectID().valueOf();
Meteor.users.update(user._id, {$addToSet : {lists : listid}});
Lists.insert({_id : listid, data : 'content'});
}
});
Meteor.publish("lists", function(){
var UserListIdsCursor = Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
if(UserListIdsCursor!=undefined){
var UserListIds = UserListIdsCursor.fetch();
return Lists.find({_id : { $in : UserListIds}});
}
});
Meteor.publish("mylists", function(){
return Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
});
//at the moment everything is allowed
Lists.allow({
insert : function(userID)
{
return true;
},
update : function(userID)
{
return true;
},
remove : function(userID)
{
return true;
}
});
}
But publishing the Lists doesn't work properly. Any ideas how to fix this? I'm also publishing "mylists" to guarantee that the user has access to the field "lists".
Solution
Lists = new Meteor.Collection('lists');
if (Meteor.isClient) {
Tracker.autorun(function() {
if (Meteor.userId()) {
Meteor.subscribe('lists');
Meteor.subscribe('myLists');
}
});
}
if (Meteor.isServer) {
Meteor.startup(function() {
if (Meteor.users.find().count() === 0) {
var user = {
username: 'test',
email: 'test#test.com',
password: 'test'
};
var userId = Accounts.createUser(user);
var listId = Lists.insert({data: 'content'});
Meteor.users.update(userId, {
$addToSet: {lists: listId}
});
}
});
Meteor.publish('lists', function() {
check(this.userId, String);
var lists = Meteor.users.findOne(this.userId).lists;
return Lists.find({_id: {$in: lists}});
});
Meteor.publish('myLists', function() {
check(this.userId, String);
return Meteor.users.find(this.userId, {fields: {lists: 1}});
});
}
Changes
Declare the Lists collection outside of the client and server (no need to declare it twice).
Ensure the user is logged in when subscribing. (performance enhancement).
When inserting the test user, use the fact that all insert functions return an id (reduces code).
Ensure the user is logged in when publishing.
Simplified lists publish function.
Fixed myLists publish function. A publish needs to return a cursor, an array of cursors, or a falsy value. You can't return an array of ids (which this code doesn't access anyway because you need to do a fetch or a findOne). Important note - this publishes another user document which has the lists field. On the client it will be merged with the existing user document, so only the logged in user will have lists. If you want all users to have the field on the client then I'd recommend just adding it to the user profiles.
Caution: As this is written, if additional list items are appended they will not be published because the lists publish function will only be rerun when the user logs in. To make this work properly, you will need a reactive join.
The real problem here is the schema.
Don't store "this user owns these lists" eg, against the users collection. Store "this list is owned by this user"
By changing your example to include an ownerId field on each List then publishing becomes easy - and reactive.
It also removes the need for the myLists publication, as you can just query client side for your lists.
Edit: If your schema also includes a userIds field on each List then publishing is also trivial for non-owners.
Solution
Lists = new Meteor.Collection('lists');
if (Meteor.isClient) {
Deps.autorun(function() {
if (Meteor.userId()) {
Meteor.subscribe('lists.owner');
Meteor.subscribe('lists.user');
}
});
}
if (Meteor.isServer) {
Lists._ensureIndex('userIds');
Lists._ensureIndex('ownerId');
Meteor.startup(function() {
if (Meteor.users.find().count() === 0) {
var user = {
username: 'test',
email: 'test#test.com',
password: 'test'
};
var userId = Accounts.createUser(user);
var listId = Lists.insert({data: 'content', ownerId: userId});
}
});
//XX- Oplog tailing in 0.7 doesn't support $ operators - split into two publications -
// or change the schema to include the ownerId in the userIds list
Meteor.publish('lists.user', function() {
check(this.userId, String);
return Lists.find({userIds: this.userId});
});
Meteor.publish('lists.owner', function() {
check(this.userId, String);
return Lists.find({ownerId: this.userId});
});
}
Meteor.users.find() returns a cursor of many items but you're trying to access .lists with
Meteor.users.find({_id: this.userId}, {limit: 1}).lists;
You need to use findOne instead.
Meteor.users.findOne({_id: this.userId}).lists;
Additionally you're running .fetch() on an array which is stored in the user collection. If this is an array of ._id fields you don't need fetch.
You can't also do .lists in your second publish because its a cursor you have to check lists client side so just use Meteor.users.find(..) on its own since you can only publish cursors.

Categories

Resources