I am trying to grab information from my firebase database after a particular intent is invoked in my conversation flow.
I am trying to make a function which takes a parameter of user ID, which will then get the highscore for that user, and then say that users highscore back to them.
app.intent('get-highscore', (conv) => {
var thisUsersHighestscore = fetchHighscoreByUserId(conv.user.id);
conv.ask('your highest score is ${thisUsersHighestScore}, say continue to keep playing.');
});
function fetchHighscoreByUserId(userId){
var highscoresRef = database.ref("highscores");
var thisUsersHighscore;
highscoresRef.on('value',function(snap){
var allHighscores= snap.val();
thisUsersHighscore = allHighscores.users.userId.highscore;
});
return thisUsersHighscore;
}
An example of the data in the database:
"highscores" : {
"users" : {
"1539261356999999924819020" : {
"highscore" : 2,
"nickname" : "default"
},
"15393362381293223232222738" : {
"highscore" : 78,
"nickname" : "quiz master"
},
"15393365724084067696560" : {
"highscore" : "32",
"nickname" : "cutie pie"
},
"45343453535534534353" : {
"highscore" : 1,
"nickname" : "friendly man"
}
}
}
It seems like it is never setting any value to thisUsersHighScore in my function.
You have a number of issues going on here - both with how you're using Firebase, how you're using Actions on Google, and how you're using Javascript. Some of these issues are just that you could be doing things better and more efficiently, while others are causing actual problems.
Accessing values in a structure in JavaScript
The first problem is that allHighscores.users.userId.highscore means "In an object named 'allHighscores', get the property named 'users', from the result of that, get the property named 'userId'". But there is no property named "userId" - there are just a bunch of properties named after a number.
You probably wanted something more like allHighscores.users[userId].highscore, which means "In an object named 'allHighscores', get the property named 'users', fromt he result of that, get the property named by the value of 'userId'".
But if this has thousands or hundreds of thousands of records, this will take up a lot of memory. And will take a lot of time to fetch from Firebase. Wouldn't it be better if you just fetched that one record directly from Firebase?
Two Firebase Issues
From above, you should probably just be fetching one record from Firebase, rather than the whole table and then searching for the one record you want. In firebase, this means you get a reference to the path of the data you want, and then request the value.
To specify the path you want, you might do something like
var userRef = database.ref("highscores/users").child(userId);
var userScoreRef = userRef.child( "highscore" );
(You can, of course, put these in one statement. I broke them up like this for clarity.)
Once you have the reference, however, you want to read the data that is at that reference. You have two issues here.
You're using the on() method, which fetches the value once, but then also sets up a callback to be called every time the score updates. You probably don't need the latter, so you can use the once() method to get the value once.
You have a callback function setup to get the value (which is good, since this is an async operation, and this is the traditional way to handle async operations in Javascript), but you're returning a value outside of that callback. So you're always returning an empty value.
These suggest that you need to make fetchHighScoreByUserId() an asynchronous function as well, and the way we have to do this now is to return a Promise. This Promise will then resolve to an actual value when the async function completes. Fortunately, the Firebase library can return a Promise, and we can get its value as part of the .then() clause in the response, so we can simplify things a lot. (I strongly suggest you read up on Promises in Javascript and how to use them.) It might look something like this:
return userScoreRef.once("value")
.then( function(scoreSnapshot){
var score = scoreSnapshot.val();
return score;
} );
Async functions and Actions on Google
In the Intent Handler, you have a similar problem as above. The call to fetchHighScoreByUserId() is async, so it doesn't finish running (or returning a value) by the time you call conv.ask() or return from the function. AoG needs to know to wait for an async call to finish. How can it do that? Promises again!
AoG Intent Handlers must return a Promise if there is an asyc call involved.
Since the modified fetchHighScoreByUserId() returns a Promise, we will leverage that. We'll also set our response in the .then() part of the Promise chain. It might look something like this:
app.intent('get-highscore', (conv) => {
return fetchHighscoreByUserId(conv.user.id)
.then( function(highScore){
conv.ask(`Your highest score is ${highScore}. Do you want to play again?`);
} );
});
Two asides here:
You need to use backticks "`" to define the string if you're trying to use ${highScore} like that.
The phrase "Say continue if you want to play again." is a very poor Voice User Interface. Better is directly asking if they want to play again.
Related
I have successfully integeraed meteor with angular2 but while fetching the data from collection facing difficulties in getting at one shot, here is the steps:
Collection Name : OrderDetails
No Of records : 1000
Server:
Created publication file to subcribe the collection:
Meteor.publish('orderFilter', function() {
return OrderLineDetails.find({});
});
Client:
this.dateSubscription =
MeteorObservable.subscribe('orderFilter').subscribe(()=> {
let lines = OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:
{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).fetch();
});
In this lines attribute fetches all the collection entries, but fails to subscribe for the changes
When I try with below one,
OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).zone().subscribe(results => {
// code to loop the results
});
In this am able to subscribe for the collection changes, but the results are looped for 1000 times , as 1000 entries in the colleciton.
Is there any way to get the whole collection entries in one single shot and mean time to subscribe the changes in the collection ?.
Yes, there are a couple of ways you can do it, mostly depending on how you want to handle the data.
If having everything at once is important, then use a Method such as:
MeteorObservable.call('getAllElements', (err, result) => {
// result.length === all elements
})
While on server side doing
Meteor.methods({
getAllElements:function(){return myCollection.find().fetch()}
})
Now, if you want to listen to changes, ofcourse you'll have to do a subscription, and if you want to lower the amount of subscriptions, use rxjs' debounceTime() function, such as (from your code):
this.theData.debounceTime(400).subscribe(value => ...., err =>)
This will wait a certain amount of time before subscribing to that collection.
Now, based on your intent: listening to changes and getting everything at once, you can combine both approaches, not the most efficient but can be effective.
As #Rager explained, observables are close to streams, so when you populate data on miniMongo (front end collection you use when you find() data and is populated when you subscribe to publications) it will start incrementing until the collection is in sync.
Since miniMongo is populated when you subscribe to a publication, and not when you query a cursor, you could either:
Try the debouceTime() approach
Use a Meteor.Method after subscribing to the publication, then sync both results, keeping the first response from the method as your starting point, and then using data from Collection.find().subscribe(collectionArray => ..., err=>) to do whatterver you want to do when changes apply (not that recommended, unless you have a specific use case for this)
Also, .zone() function is specific to force re-render on Angular's event cycle. I'd recomend not use it if you're processing the collections' data instead of rendering it on a ngFor* loop. And if you're using an ngFor* loop, use the async pipe instead ngFor="let entry of Collection | async"
I don't think that's possible. When you subscribe to an Observable it handles values as a "stream", not necessarily a loop. I have seen some makeshift helper methods that handle the data synchronously, though the time it takes to subscribe is not decreased. Check out this article for an under the hood look... A simple Observable implementation
However, you can set it up to only loop once.
The way that I've been setting up that scenario, the collection only gets looped through one time (in the constructor when the app starts) and detects changes in the collection. In your case it would look like:
values: YourModel[] = []; //this is an array of models to store the data
theData: Observable<YourModel[]>;
errors: string[];
subFinished: boolean = false;
constructor(){
this.theData = OrderDetails.find({expectedShipDate:{$in:strArr}},{fields:{"expectedShipDate":1,"loadNo":1},sort:{"expectedShipDate":1}}).zone();
MeteorObservable.subscribe('orderFilter').subscribe();
//push data onto the values array
this.theData.subscribe(
value => this.values = value,
error => this.errors.push("new error"),
() => this.subFinished = true
);
}
The "values" array is updated with whatever changes happen to the database.
I've made a function that calls on the FireBase database and will return a MutableList. However, when I try to make it return on a specific line, it says it requires a Unit instead of the MutableList.
fun firebaseCollect(key: String): MutableList<CustomList> {
var ref = FirebaseDatabase.getInstance().getReference(key)
var lessonList = mutableListOf<CustomList>()
ref.addValueEventListener(object: ValueEventListener{
override fun onCancelled(p0: DatabaseError?) {
}
override fun onDataChange(p0: DataSnapshot?) {
if (p0!!.exists()) {
lessonList.clear()
for (index in p0.children) {
val lesson = index.getValue(CustomList::class.java)
lessonList.add(lesson!!)
}
return lessonList
}
}
})
return lessonList
}
Type mismatch. Required: Unit, Found: MutableList< CustomList > is found at the first return lessonList since what I am asking for it to return is a MutableList not a Unit. I am confused as to why this happens. The last return would give an empty list. It is currently my first jab at FireBase and this is a practice I am doing. The rules for read and write have been set to public as well. How should I recode the function that I am able to return the data from FireBase into the function and passed back to the caller?
Firebase APIs are asynchronous. For your case, that means addValueEventListener returns immediately. Then, some time later, the listener you passed to it will be invoked with the data you're looking for. Your return statement in the callback doesn't actually return any data to the caller. In fact, you can't return anything from that callback. At the bottom of your function, when you return lessonList, you're actually returning an initially empty list to the caller, which may change later when the data finally arrives.
To get a better sense of how your code works, put log lines in various places, and see for yourself the order in which the code is invoked. You can read more about why Firebase APIs are asynchronous by reading this article. The bottom line is that you'll need to interact with the asynchronous APIs using asynchronous programming techniques. Don't try to make them synchronous.
Data is loaded asynchronously from Firebase. Once the data is fetched the method onDatachange() is invoked.
You are returning lessonList inside onDatachange(). Return type of onDatachange() is void(Unit in kotlin). This is the reason for the type mismatch error.
For returning the result from the method onDatachange() try this.
I have a Meteor Helper that does a GET request and am supposed to get response back and pass it back to the Template, but its now showing up the front end. When I log it to console, it shows the value corerctly, for the life of mine I can't get this to output to the actual template.
Here is my helper:
UI.registerHelper('getDistance', function(formatted_address) {
HTTP.call( 'GET', 'https://maps.googleapis.com/maps/api/distancematrix/json? units=imperial&origins=Washington,DC&destinations='+formatted_address+'&key=MYKEY', {}, function( error, response ) {
if ( error ) {
console.log( error );
} else {
var distanceMiles = response.data.rows[0].elements[0].distance.text;
console.log(response.data.rows[0].elements[0].distance.text);
return distanceMiles;
}
});
});
In my template I pass have the following:
{{getDistance formatted_address}}
Again, this works fine and shows exactly what I need in the console, but not in the template.
Any ideas what I'm doing wrong?
I posted an article on TMC recently that you may find useful for such a pattern. In that article the problem involves executing an expensive function for each item in a list. As others have pointed out, doing asynchronous calls in a helper is not good practice.
In your case, make a local collection called Distances. If you wish, you can use your document _id to align it with your collection.
const Distances = new Mongo.collection(); // only declare this on the client
Then setup a function that either lazily computes the distance or returns it immediately if it's already been computed:
function lazyDistance(formatted_address){
let doc = Distances.findOne({ formatted_address: formatted_address });
if ( doc ){
return doc.distanceMiles;
} else {
let url = 'https://maps.googleapis.com/maps/api/distancematrix/json';
url += '?units=imperial&origins=Washington,DC&key=MYKEY&destinations=';
url += formatted_address;
HTTP.call('GET',url,{},(error,response )=>{
if ( error ) {
console.log( error );
} else {
Distances.insert({
formatted_address: formatted_address,
distanceMiles: response.data.rows[0].elements[0].distance.text
});
}
});
}
});
Now you can have a helper that just returns a cached value from that local collection:
UI.registerHelper('getDistance',formatted_address=>{
return lazyDistance(formatted_address);
});
You could also do this based on an _id instead of an address string of course. There's a tacit assumption above that formatted_address is unique.
It's Meteor's reactivity that really makes this work. The first time the helper is called the distance will be null but as it gets computed asynchronously the helper will automagically update the value.
best practice is not to do an async call in a helper. think of the #each and the helper as a way for the view to simply show the results of a prior calculation, not to get started on doing the calculation. remember that a helper might be called multiple times for a single item.
instead, in the onCreated() of your template, start the work of getting the data you need and doing your calculations. store those results in a reactive var, or reactive array. then your helper should do nothing more than look up the previously calculated results. further, should that helper be called more times than you expect, you don't have to worry about all those additional async calls being made.
The result does not show up because HTTP.call is an async function.
Use a reactiveVar in your case.
Depending on how is the formated_address param updated you can trigger the getDistance with a tracker autorun.
Regs
Yann
I'd like to create a method that returns the count of a generic collection.
Calling the method would look something like this:
Meteor.call('getCollectionCount', 'COLLECTION_NAME');
And the result would be the collection count.
The server method code would look something like this:
getCollectionCount: function (collectionName) {
return window[collectionName].find().count();
}
This won't work because window isn't defined on the server, but is something similar possible?
Use global instead of window.
Note that this uses the variable name assigned to the collection object, not the name given to the collection. For this to work with Meteor.users you need to assign another variable name.
if (Meteor.isServer) {
users = Meteor.users;
}
if (Meteor.isClient) {
Meteor.call('count', 'users', function (err, res) {
// do something with number of users
});
}
Also probably a good idea to check that global[collectionName] is actually a collection.
I came up with this code which makes the following assumptions :
collections are declared in the global scope as top level objects.
collections are searched by collection name, not the collection variable identifier.
So client code should declare their collections like this :
MyCollection=new Meteor.Collection("my-collection");
And use the function like this :
var clientResult=Meteor.call("getCollectionCount","my-collection",function(error,result){
if(error){
console.log(error);
return;
}
console.log("actual server-side count is : ",result);
});
console.log("published subset count is : ",clientResult);
The method supports execution on the client (this is known as method stub or method simulation) but will only yield the count of the collection subset replicated client-side, to get the real count wait for server-side response using a callback.
/packages/my-package/lib/my-package.js
getCollection=function(collectionName){
if(collectionName=="users"){
return Meteor.users;
}
var globalScope=Meteor.isClient?window:global;
for(var property in globalScope){
var object=globalScope[property];
if(object instanceof Meteor.Collection && object._name==collectionName){
return object;
}
}
throw Meteor.Error(500,"No collection named "+collectionName);
};
Meteor.methods({
getCollectionCount:function(collectionName){
return getCollection(collectionName).find().count();
}
});
As Meteor.users is not declared as a top level variable you have to account for the special case (yes, this is ugly).
Digging into Meteor's collection handling code could provide a better alternative (getting access to a collection handle by collection name).
Final words on this : using a method call to count a collection documents is unfortunately non-reactive, so given the Meteor paradigm this might be of little use.
Most of the time you will want to fetch the number of documents in a collection for pagination purpose (something like a "Load more" button in a posts list for example), and as the rest of the Meteor architecture you'll want this to be reactive.
To count documents in a collection reactively you'll have to setup a slightly more complicated publication as showcased in the "counts-by-room" example in the docs.
http://docs.meteor.com/#meteor_publish
This is something you definitely want to read and understand.
This smart package is actually doing it right :
http://atmospherejs.com/package/publish-counts
It provides a helper function that is publishing the counts of any cursor.
Keep track of the collections on some other property that the server has access too. You could even call it window if you really wanted to.
var wow = new Meteor.Collection("wow");
collections["wow"] = wow;
getCollectionCount: function (collectionName) {
return collections[collectionName].find().count();
}
If you don't want the package users to change how they work with collections in the app then I think you should use MongoInternals to get collections by name from the db. Not tested but here is an example:
//on server
Meteor.methods({
count: function( name ){
var db = MongoInternals.defaultRemoteCollectionDriver().mongo.db;
var collection = db.collection( name );
return collection && collection.count({});
}
});
Another example of MongoInternals use is here. Documentation of the count() function available from the mongo driver is here.
I'm trying to work with associating documents in different collections (not embedded documents) and while there is an issue for that in Mongooose, I'm trying to work around it now by lazy loading the associated document with a virtual property as documented on the Mongoose website.
The problem is that the getter for a virtual takes a function as an argument and uses the return value for the virtual property. This is great when the virtual doesn't require any async calls to calculate it's value, but doesn't work when I need to make an async call to load the other document. Here's the sample code I'm working with:
TransactionSchema.virtual('notebook')
.get( function() { // <-- the return value of this function is used as the property value
Notebook.findById(this.notebookId, function(err, notebook) {
return notebook; // I can't use this value, since the outer function returns before we get to this code
})
// undefined is returned here as the properties value
});
This doesn't work since the function returns before the async call is finished. Is there a way I could use a flow control library to make this work, or could I modify the first function so that I pass the findById call to the getter instead of an anonymous function?
You can define a virtual method, for which you can define a callback.
Using your example:
TransactionSchema.method('getNotebook', function(cb) {
Notebook.findById(this.notebookId, function(err, notebook) {
cb(notebook);
})
});
And while the sole commenter appears to be one of those pedantic types, you also should not be afraid of embedding documents. Its one of mongos strong points from what I understand.
One uses the above code like so:
instance.getNotebook(function(nootebook){
// hey man, I have my notebook and stuff
});
While this addresses the broader problem rather than the specific question, I still thought it was worth submitting:
You can easily load an associated document from another collection (having a nearly identical result as defining a virtual) by using Mongoose's query populate function. Using the above example, this requires specifying the ref of the ObjectID in the Transaction schema (to point to the Notebook collection), then calling populate(NotebookId) while constructing the query. The linked Mongoose documentation addresses this pretty thoroughly.
I'm not familiar with Mongoose's history, but I'm guessing populate did not exist when these earlier answers were submitted.
Josh's approach works great for single document look-ups, but my situation was a little more complex. I needed to do a look-up on a nested property for an entire array of objects. For example, my model looked more like this:
var TransactionSchema = new Schema({
...
, notebooks: {type: [Notebook]}
});
var NotebookSchema = new Schema({
...
, authorName: String // this should not necessarily persist to db because it may get stale
, authorId: String
});
var AuthorSchema = new Schema({
firstName: String
, lastName: String
});
Then, in my application code (I'm using Express), when I get a Transaction, I want all of the notebooks with author last name's:
...
TransactionSchema.findById(someTransactionId, function(err, trans) {
...
if (trans) {
var authorIds = trans.notebooks.map(function(tx) {
return notebook.authorId;
});
Author.find({_id: {$in: authorIds}, [], function(err2, authors) {
for (var a in authors) {
for (var n in trans.notebooks {
if (authors[a].id == trans.notebooks[n].authorId) {
trans.notebooks[n].authorLastName = authors[a].lastName;
break;
}
}
}
...
});
This seems wildly inefficient and hacky, but I could not figure out another way to accomplish this. Lastly, I am new to node.js, mongoose, and stackoverflow so forgive me if this is not the most appropriate place to extend this discussion. It's just that Josh's solution was the most helpful in my eventual "solution."
As this is an old question, I figured it might use an update.
To achieve asynchronous virtual fields, you can use mongoose-fill, as stated in mongoose's github issue: https://github.com/Automattic/mongoose/issues/1894