Limit wp-rest-api posts request to an array of postID's - wordpress

My complete project will need to return the nearest-posts to the given geolocation.
I succeeded in doing this by building a custom callback function seeking the nearest posts but also building a custom array containing the data-fields of those nearest posts. If I define this function as the callback function of my route all works great!
All ok but I would like the return to be a copy of the default post-return, of course limited to the nearest posts. And not a set of custom defined fields.
I can't seem to figure out how to do this.
Before going into details I would like to know which way to go. My plan, that doesn't work, was :
extend WP_REST_Posts_Controller
register (and initialize) a new route
define a custom callback function in this new route
the custom callback function builds an array of postID's that are the nearest (lat&long was passed in the http request)
So far so good but now comes my problem.
How do I pass the array of postIDs to the get_items method of WP_REST_Posts_Controller in order to get a "default" return of specific(nearest) posts
Clearly a simple approach like this doesn't work
public function custom_callback_function($geoloc_array) {
// call the function get_nearest_items($geoloc_array)
// that returns an array of postIDs'
// something like : [163,49]
$nearest_poi_array = get_nearest_items($geoloc_array);
// put the array of postIDs to fetch in "include" of a new array
// hand over new array to the get_items method of WP_REST_posts_controller
$posts_to_return['include'] = $nearest_poi_array;
return get_items($posts_to_return);
}
If somebody could point me to the right direction I would be grateful!!

Related

Update an object with notation with a parameter?

In my firebase i have a collection, inside there is a document, and inside there is an object :
object 1
key1:value
key2:value
key3:value
I would like to only update certain keys inside an object say
object1 - key1 and key2.
to do that, i need notation.
the problem is that I pass a parameter to the function that save :
function updateit(product,target)
{
db.collection("Stores").doc(target).update({
product
})
So here if I pass a product that contains only key 1, it will override the previous.
So, I tried to pass this object with notation :
product["product"+".title"] = "xxxxx"; // a new pair in product to pass
and it didn't work, it will save a new object (override) with fields like :
product
product.title=xxxxx
How should you do such a simple thing ?
ok obviously, this is the answer :
db.collection("Stores").doc(targetStore).update(
product // no {} around 'product', not as object!
)
see the comment that explains it all.

Retrieve values from firebase database in conversation flow

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.

Returned value from Meteor Helper not showing up in template

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

What's the cleanest way to add a hook on certain path changes?

I'd like to add a hook function when the "path namespace" changes. For example, when the router goes from myapp/fields/9837278993 to myapp/lists/183727856. Of course I could use some regex conditional logic in the main onBeforeAction hook. The more abstract logic would be "when namespace goes from fields to any except fields run the hook. What are your suggestions? Thanks for your help!
I think you're on the right track wanting to abstract the logic away from the router layer (makes switching routers much easier!). The problem is getting the data context before it changes.
To accomplish this, I'd recommend turning your Router.go line into a function. Chances are this is inside an event, so the IR reactive current() won't hurt you here.
function changePath(path) {
if (path !== 'fields' && Router.current().route.name === 'fields') {
//do stuff
}
Router.go(path);
}
I ended up with my own fully generic solution, using the reactive var Router.current().location.get() mixed with the Tracker.autorun function. So It's 100% independant of Router configuration, which I think is a good thing. Written in coffeescript.
Trivial to use :
Meteor.startup ->
RouterTransitions.register('/lists/' ,null,-> console.log("Switching from lists to any"))
Expected behaviour :
When the route changes from /lists/* to /(*) where (*)!='lists/', the hook function is run.
You can register as many transitions as you want from the same origin, as long as the destination is not a subpath of the origin (for example : from /lists/ to /lists/1864f will not trigger the hook). However the origin can be a subpath of the destination.
Here is the source :
class RouterTransitions
#could be any sequence of characters with at least one forbidden in URL standards (RFC 3986, section 2 characters)
WILDCARD:'>>'
constructor:->
Tracker.autorun =>
newPath=Router.current()?.location.get().path
if #_oldPath!=newPath
#_matchingDestinations.forEach (destinations) =>
origin=destinations.origin
Object.keys(destinations.targets).forEach (key) =>
if !newPath.match("#{origin}.*")&&(key==#WILDCARD or newPath.match("#{key}.*"))
#call the hook
destinations.targets[key]()
#_matchingOrigins =Object.keys(#dictionnary).filter (origin) => newPath.match("#{origin}.*")
#_matchingDestinations = #_matchingOrigins.map (key)=> {
targets:#dictionnary[key]
origin:key
}
#_oldPath=newPath
###
#param {String} origin : the namespace of the incoming path, null for any match. Origin can be a subset of destination.
#param {String} destination : the namespace of the forthcoming path, null for any match. Important! destination cannot be a subset of origin
#param {String} hook : the callback to be run on matching conditions
###
register:(origin,destination,hook) =>
origin=origin||#WILDCARD
destination=destination||#WILDCARD
if #dictionnary[origin]
#dictionnary[origin][destination]=hook
else
hooks=#dictionnary[origin]={}
hooks[destination]=hook
#A simple dict with keys='origin' and values plain objects with destinations mapped to hooks
dictionnary:{}
_oldPath:'/'
_matchingDestinations:[]
_matchingOrigins:[]
window.RouterTransitions=new RouterTransitions()

Meteor: Get count of collection by name. Accessing global scope on server

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.

Resources