Cloud Functions for Firebase - get parent data from database trigger - firebase

Is there a clean built in way of directly referencing the data value of the node above the database trigger? I understand I can get a parent ref which I could then query for the value, but if there was a more concise way of doing this that would be great thanks.
For clarity, I want to use a child node within an object as a trigger, and when it occurs get the value of the parent object directly, to avoid the function being invoked when other changes are made to the parent object like so:
const parentObject = {
triggerValue: 'I want the function to be triggered only on writes to this path',
parentValue: 'But I also want this value',
}
Thanks

I've googled for this answer like six times and keep having to re-implement the solution.
Here's how to get the parent data, post, from a child attribute, post.body, that's changed:
exports.doStuff = functions.database
.ref("/posts/{postId}/body")
.onWrite(event => {
return event.data.ref.parent.once("value").then(snap => {
const post = snap.val();
// do stuff with post here
});
});

You can use event.data.ref and event.data.adminRef to navigate the database. They are Reference objects and work exactly like they would if you were building a web app with Firebase on the client side. So, if using the parent property to navigate up a level works fine for you, then just use that. There's no additional special syntax.

Related

Where to call IONIC SQLLITE select statement

I have a DB function as shown below:
If I want to use the results of this query in a page module, where do I call this function? Do I call it in the constructor or the lifecycle method (ionViewDidLoad, ionViewDidEnter). Please advise.
Currently I call it in the constructor but it seems like it slows down the app. I am not sure if I am doing the right thing
getAddressDetails() {
return new Promise((resolve, reject) => {
let sql = "select * from tablename"
this.database.executeSql(sql, []).then((data) => {
resolve(data)}
});
}```
(...) main difference between the constructor and the ionViewDidLoad
(...) is that sometimes you want to interact with the DOM (maybe to
initialize a map).
In that case, if you try to access the DOM in the constructor, you
will notice that the DOM is not ready by that point and you won't be
able to get the map element. The correct approach to do it would be
inside the ionViewDidLoad (...).
See this answer.
constructor is called before all, once per instantiation of the
page, here you can do initialization that does not refer the HTML DOM
ionViewDidLoad is called when the page DOM has been loaded, before
than the page is shown, also a single time per page instantiation,
here you can do initialization thet needs the HTML DOM to be ready
And see this answer.

Correctly updating the same object on create trigger in firebase realtime DB trigger

I have a firebase realtime database trigger on a create node. my need is to update a property based on some condition in the create trigger for the same object. The way i am doing currently is below:
exports.on_order_received_validate_doodle_cash_order = functions.database.ref("/orders/{id}")
.onCreate((change, context) => {
console.log("start of on_order_received_deduct_doodle_cash")
const orderId = context.params.id
const order = change.val();
var db = admin.database();
const orderRef = db.ref('orders/')
return orderRef.child(orderId).update({"_verifiedOrder": true})
})
As you can see i am getting order id from context and then querying object again and updating it. My question is do i need to do this circus or can i just update it without querying again?
Generally it looks good. Just some small feedback to make you feel more confident about being on the right track.
Call the parameter snapshot instead of change because the parameter name change only make sense for the onUpdate event trigger.
You do not need to log that you're entering the function. Because entering and leaving the function is automatically logged by Firebase also.
You can remove the order variable that is unused.
You are actually not "querying" the object again. Making a reference to a node in the database doesn't make any network call itself. Not until you subscribe to receiving data. So doing orderRef.child(orderId) is not a query, it's just a database reference.
You can use the snapshot's own reference attribute to shorten your code a bit... effectively throwing away almost all code :-)
So your code code look like this instead. It is doing the exact same thing, just shorter. It was also correct from the beginning.
exports.on_order_received_validate_doodle_cash_order = functions
.database
.ref("/orders/{id}")
.onCreate((snapshot) => {
return snapshot.ref.child("_verifiedOrder").set(true);
});
But as mentioned in my comment above, you are effectively just setting a flag that is confirming that data was saved (or rather: confirming that the function was triggered). You might want to add some logic in there to check whether the order can be placed or not and then set the verified flag to true or false depending on that. Because with the logic of the implementation, all orders will have the value _verifiedOrder set to true, which is a waste of storage in your database.

Displaying data from Firebase on load of Ionic app

I'm a beginner in Ionic and Firebase. To learn using ionic+firebase, I'm writing a RandomQuote app to fetch a random entry from Firebase. A reload() method is called when I click a reload button, and the random quote is displayed as expected.
However, I also want the quote to display when the app is loaded, i.e., before I click the reload button. I call the reload() method in the constructor but it doesn't work. I have tried to search for answers on the web but cannot find anything that I could understand. Not sure if I'm searching the wrong keywords or in the wrong domains.
The following is the reload() method that I put in my FirebaseProvider class and called from my home.ts:
reload(){
this.afd.list('/quoteList/').valueChanges().subscribe(
data => {
this.oneQuote = data[Math.floor(Math.random() * data.length)];
}
)
return this.oneQuote;
}
Can anyone give me some hints? Or any pointer to useful books / materials for beginners will also be highly appreciated. Thank you very much.
Data is loaded from Firebase asynchronously. This means that by the time your return statement runs this.oneQuote doesn't have a value yet.
This is easiest to say by placing a few log statements around your code:
console.log("Before subscribing");
this.afd.list('/quoteList/').valueChanges().subscribe(
data => {
console.log("Got data");
}
)
console.log("After subscribing");
When you run this code, the output is:
Before subscribing
After subscribing
Got data
This is probably not what you expected. But it completely explains why your return statement doesn't return the data: that data hasn't been loaded yet.
So you need to make sure your code that needs the data runs after the data has been loaded. There are two common ways to do this:
By moving the code into the callback
By returning a promise/subscription/observable
Moving the code into the callback is easiest: when the console.log("Got data") statement runs in the code above, the data is guaranteed to be available. So if you move the code that requires the data into that place, it can use the data without problems.
Returning a promise/subscription/observable is a slightly trickier to understand, but nicer way to doing the same. Now instead of moving the code-that-needs-data into the callback, you'll return "something" out of the callback that exposes the data when it is available. In the case of AngularFire the easiest way to do that is to return the actual observable itself:
return this.afd.list('/quoteList/').valueChanges();
Now the code that needs the quotes can just subscribe to the return value and update the UI:
reload().subscribe(data => {
this.oneQuote = data[Math.floor(Math.random() * data.length)];
}
A final note: having a reload() method sounds like an antipattern. The subscription will already be called whenever the data in the quoteList changes. There is no need to call reload() for that.

How can I make a Meteor publish method reactive to a server-side parameter?

I have a publication based on server-side user permissions. I want it to be reactive to changes in these permissions.
// SERVER CODE
Meteor.publish("my_publication", function(parent_id) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
ChildCollection.find({parent_id: parent_id, permissions: {$in: myPermissionsList}})
}
// CLIENT CODE
Tracker.autorun(function () {
Meteor.subscribe('my_publication', Session.get("my_parent_id"));
});
This properly returns all the elements of the "child" collection specified parent, as long as the parent says the user has at least one of the permissions in the child element's list. It does this without the user actually knowing what their permissions are, which is a requirement.
This behaves like one would expect in Meteor:
The subscription does automatically update if any of the returned ChildCollection elements are changed.
The subscription does automatically update if the client changes the "my_parent_id" Session variable, triggering the Tracker.autorun resubscribe.
The subscription does not automatically update if the permissions used to make the query (parent.permissionsDict[this.userId]) are changed.
We're looking for the best (highest performing) way to get an automatic update in the last case.
This article was a helpful, more detailed resource on the topic:
https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
My current understanding is that I need to utilize cursor.observeChanges() to react to changes in my permissions query. However, I am not sure how this fits into the rest of the Meteor publish/subscribe model--where would I call this, and how could the callback instruct Meteor to republish "my_publication"?
I believe https://atmospherejs.com/mrt/reactive-publish addresses this, but I feel like I should try to get a better grasp on core reactivity in meteor before turning to an external package. I also lack an understanding about the performance costs.
Any help would be greatly appreciated!
You can use the reactive-publish package (I am one of authors):
Meteor.publish("my_publication", function(parent_id) {
this.autorun(function (computation) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id}, {fields: {permissionsDict: 1}});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
return ChildCollection.find({parent_id: parent._id, permissions: {$in: myPermissionsList}});
});
}
It is important that you limit the fields you are interested in the parent document, otherwise autorun would rerun every time any field changes in the document, even if you do not care/use that field.

angularFire $asObject doesn't push

I am new to firebase and trying to use the $asObject as in the angulerFire doc. Basically, I have profile as follows below. I use $asObject to update the email. However when I use $save(), it replaces the entire profile with only the email, rather than pushing it to the end of list ie it works like set() rather than push(). Is how it is meant to work? how do I only push?
Object
{profiles:
{peterpan:
{name:"Peter Trudy", dob:"7th March"}
}
}
My click function:
$scope.angularObject = function(){
var syncProfile = $firebase(ref.child("profiles").child("peterpan"));
var profileObject = syncProfile.$asObject();
profileObject.email= "peter#peterpan.com";
profileObject.$save();
};
You're looking for $update:
syncProfile.$update({ "email": "peter#peterpan.com" });
Note that $update is only available on $firebase and not on the FirebaseObject that you get back from $asObject. The reason for this is that $asObject is really meant as an object that is bound directly to an angular scope. You're not expected to perform updates to it in your own code.
By the way: if the rest of your code is using AngularFire in a similar way, you might consider dropping AngularFire and using Firebase's JavaScript SDK directly. It is much simpler to use, since it doesn't need to mediate between Firebase and Angular's way of working.

Resources