Querying a parse table and eagerly fetching Relations for matching - javascript

Currently, I have a table named Appointments- on appointments, I have a Relation of Clients.
In searching the parse documentation, I haven't found a ton of help on how to eagerly fetch all of the child collection of Clients when retrieving the Appointments. I have attempted a standard query, which looked like this:
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User",Parse.User.current());
query.include('Rate'); // a pointer object
query.find().then(function(appointments){
let appointmentItems =[];
for(var i=0; i < appointments.length;i++){
var appt = appointments[i];
var clientRelation = appt.relation('Client');
clientRelation.query().find().then(function(clients){
appointmentItems.push(
{
objectId: appt.id,
startDate : appt.get("Start"),
endDate: appt.get("End"),
clients: clients, //should be a Parse object collection
rate : appt.get("Rate"),
type: appt.get("Type"),
notes : appt.get("Notes"),
scheduledDate: appt.get("ScheduledDate"),
confirmed:appt.get("Confirmed"),
parseAppointment:appt
}
);//add to appointmentitems
}); //query.find
}
});
This does not return a correct Clients collection-
I then switched over to attempt to do this in cloud code- as I was assuming the issue was on my side for whatever reason, I thought I'd create a function that did the same thing, only on their server to reduce the amount of network calls.
Here is what that function was defined as:
Parse.Cloud.define("GetAllAppointmentsWithClients",function(request,response){
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User", request.user);
query.include('Rate');
query.find().then(function(appointments){
//for each appointment, get all client items
var apptItems = appointments.map(function(appointment){
var ClientRelation = appointment.get("Clients");
console.log(ClientRelation);
return {
objectId: appointment.id,
startDate : appointment.get("Start"),
endDate: appointment.get("End"),
clients: ClientRelation.query().find(),
rate : appointment.get("Rate"),
type: appointment.get("Type"),
notes : appointment.get("Notes"),
scheduledDate: appointment.get("ScheduledDate"),
confirmed:appointment.get("Confirmed"),
parseAppointment:appointment
};
});
console.log('apptItems Count is ' + apptItems.length);
response.success(apptItems);
})
});
and the resulting "Clients" returned look nothing like the actual object class:
clients: {_rejected: false, _rejectedCallbacks: [], _resolved: false, _resolvedCallbacks: []}
When I browse the data, I see the related objects just fine. The fact that Parse cannot eagerly fetch relational queries within the same call seems a bit odd coming from other data providers, but at this point I'd take the overhead of additional calls if the data was retrieved properly.
Any help would be beneficial, thank you.

Well, in your Cloud code example - ClientRelation.query().find() will return a Parse.Promise. So the output clients: {_rejected: false, _rejectedCallbacks: [], _resolved: false, _resolvedCallbacks: []} makes sense - that's what a promise looks like in console. The ClientRelation.query().find() will be an async call so your response.success(apptItems) is going to be happen before you're done anyway.
Your first example as far as I can see looks good though. What do you see as your clients response if you just output it like the following? Are you sure you're getting an array of Parse.Objects? Are you getting an empty []? (Meaning, do the objects with client relations you're querying actually have clients added?)
clientRelation.query().find().then(function(clients){
console.log(clients); // Check what you're actually getting here.
});
Also, one more helpful thing. Are you going to have more than 100 clients in any given appointment object? Parse.Relation is really meant for very large related collection of other objects. If you know that your appointments aren't going to have more than 100 (rule of thumb) related objects - a much easier way of doing this is to store your client objects in an Array within your Appointment objects.
With a Parse.Relation, you can't get around having to make that second query to get that related collection (client or cloud). But with a datatype Array you could do the following.
var query = new Parse.Query(Appointment);
query.equalTo("User", request.user);
query.include('Rate');
query.include('Clients'); // Assumes Client column is now an Array of Client Parse.Objects
query.find().then(function(appointments){
// You'll find Client Parse.Objects already nested and provided for you in the appointments.
console.log(appointments[0].get('Clients'));
});

I ended up solving this using "Promises in Series"
the final code looked something like this:
var Appointment = Parse.Object.extend("Appointment");
var query = new Parse.Query(Appointment);
query.equalTo("User",Parse.User.current());
query.include('Rate');
var appointmentItems = [];
query.find().then(function(appointments){
var promise = Parse.Promise.as();
_.each(appointments,function(appointment){
promise = promise.then(function(){
var clientRelation = appointment.relation('Clients');
return clientRelation.query().find().then(function(clients){
appointmentItems.push(
{
//...object details
}
);
})
});
});
return promise;
}).then(function(result){
// return/use appointmentItems with the sub-collection of clients that were fetched within the subquery.
});
You can apparently do this in parallel, but that was really not needed for me, as the query I'm using seems to return instantaniously. I got rid of the cloud code- as it didnt seem to provide any performance boost. I will say, the fact that you cannot debug cloud code seems truly limiting and I wasted a bit of time waiting for console.log statements to show themselves on the log of the cloud code panel- overall the Parse.Promise object was the key to getting this to work properly.

Related

Formatting data from Firebase with HTML and CSS

This time I'm facing a problem with formatting data from Firebase. This is how structure in my database looks:
I'm getting it from database using this code:
const preObject = document.getElementById('object')
var parametryObject = firebase.database()
.ref('Parametry_powietrza')
.limitToLast(1)
.once('value').then(function(snapshot) {
var listaParametrow = snapshot.val();
console.log(listaParametrow);
preObject.innerText = JSON.stringify(snapshot.val(), null, 3)
});
And on my webpage it looks like:
My question is - how to properly refer to that data to be able to change its appearance using HTML and CSS?
Thank you! :)
It looks like you're trying to access the data inside your JSON object being returned to you from your FireBase RealTime database (RTDB). But the way you've structured your data makes it near impossible for your javascript to iterate through it.
Some pointers I can give you regarding your data in the Realtime Database atm:
1) Datetime is typically stored in what's called Epoch Time. Which is typically the number of seconds since Jan 1, 1970. The number can easily be converted back into text using various javascript time libraries. An easy one to try out is Luxon. You can see epoch time with this online convertor here.
2) Secondly, RTDB supports the creation of unique, sequential, sortable "push-id" whenever you call the .push({ myDataObject }) function. So there's no need to store the date and the time as the "keys" to your object. More info about the push-id here and here. It's really interesting stuff!
3) I hate to be writing this suggestion because it seems like taking a step back before you can take steps forward, but I feel like you would benefit alot on looking at some articles on designing databases and how to sensibly structure your data. Firebase also has a great introduction here. If it's any help, for your data structure, I suggest modifying your data structure to something like below:
{
Parametry_powietrza: {
[firebase_push_id]: {
timestamp: 726354821,
Cisnienie: 1007.78,
Temperatura: 19.23,
Wilgotnosc: 52.00,
},
[firebase_push_id]: {
timestamp: 726354821,
Cisnienie: 1007.78,
Temperatura: 19.23,
Wilgotnosc: 52.00,
}
}
}
That way, when firebase returns your data, you can iterate through the data much more easily and extract the information you need like:
database
.ref('Parametry_powietrza')
.limitToLast(10)
.once('value', snapshot => {
snapshot.forEach(child => {
// do what you need to do with the data
console.log("firebase push id", child.key);
console.log("data", child.val());
})
});
All the best! BTW are you using any javascript frameworks like React or Vue?

Display Firebase data appended with jquery to a table in order

I am currently trying to append data through I pull from Firebase to a table in the order of newest to oldest posts. I currently have the following setup in my code base (simplified to address issue):
var theDataRef = new Firebase('https://my-app.firebaseio.com');
theDataRef.orderByChild("timestamp").limitToLast(25).on('child_added', function (snapshot) {
var message = snapshot.val();
displaytableRow(message.name, message.text);
}
function displaytableRow(name, message) {
$("#sch").find('tbody > tr:first')
.before($("<tr><td><div>" + name + ":" + message + "</div></td></tr>"))
};
I have tried to create a table that displays newest to oldest data by using both firebase and jquery techniques but every time my data is displayed in a random order. I have a working timestamp field on every record in my data as well but even ordering by that does not solve the problem. Has anybody had any experience building this successfully?
EDIT:
The timestamp is gotten with the following code:
var timestamp = Firebase.ServerValue.TIMESTAMP;
And the database architecture is structured like so:
If you want to order your posts from newest to oldest you can do the following:
1/ Store a field in your post that is the inverse of the TimeStamp as follows:
var tDate = new Date().getTime();
var postData = {
//.....
timestampInverted: (0 - tDate),
//.....
};
2/ Query your posts ordered by this field as follows:
theDataRef.orderByChild("timestampInverted").limitToLast(25).once('value', function(snapshot) {
snapshot.forEach(function(childSnapshot) {
displaytableRow(childSnapshot.val().name, childSnapshot.val().text);
});
});
It is important to note that theDataRef.orderByChild("timestampInverted").limitToLast(25) returns a Query, which returns a DataSnapshot : Therefore you need to use snapshot.forEach() (see doc here) to iterate over the different posts items.
Also note that "even when there is only a single match for the query, the snapshot is still a list; it just contains a single item. To access the item, you always need to loop over the snapshot".

Script works in development environment, but not in production

I am running an angular app. I am having the strangest effect ever...
I am calling a backend which returns a json. I parse that json and build an object structure client side. It works perfectly in dev but the exact same code does provide strange effects on prod. See code inline comments for hints. The only thing I could think of is that the data comes different from prod...
I can't see what's wrong, as it's the exact same code, and it's driving me completely nuts, probably the worse thing I ever saw in 10+ years programming!
Basically the json structure is a list of objects, every object has a reference ID, and several objects are correlated by the same reference ID - I need a structure where I'd access all objects with the same reference ID.
Maybe I'll make a fool of myself here but I really can't see it...I just ran the data in two JSON validators and both say the data is valid.
app.factory('ItemService', ['ItemProvider', function(itemProvider) {
var itemSrv;
var obj_by_id = {}; //create empty object
itemSrv = function(callback) {
itemProvider.get_data()
.success(function(data) { // callback ok, data has json content
for (var i=0; i<data.length; i++) {
obj = data[i]; // I get the object in the json
if (! (obj.identificador in obj_by_id)) {
obj_by_id[obj.identificador] = {}; //build a key in the object if not already available
}
obj_by_id[obj.identificador][obj.campo_nombre] = obj; //assign the object by keys
console.log(obj_by_id); **//HERE obj_by_id is ALWAYS EMPTY!!!! BUT ONLY ON PROD!!! On dev works fine...**
}
callback(obj_by_id); //Here I would get the whole structure, but it's empty on prod...
})
.error(function(data) {
console.log("Error getting data from server");
});
}
//factory function body that constructs shinyNewServiceInstance
return itemSrv;
}]);
EDIT: console.log(data) right after success, on request
dev:
http://imgur.com/10aQsx2,rJJw2bb#0
prod:
http://imgur.com/10aQsx2,rJJw2bb#1
EDIT2: you can have a look at the returned data here (will remove this link later!): http://bibliolabs.cc/milfs/api.php/ison?id=2
I am concerned about all those \u unicode chars, could that be an issue?

Backbone collection fetch imported incorrectly

I have a collection which is fetched from a REST endpoint, where it receives a JSON.
So to be completely clear:
var Products = Backbone.Collection.extend({
model: Product,
url : 'restendpoint',
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var products = new Products();
products.fetch();
If I log this, then I have the data. However, the length of the object (initial) is 0, but it has 6 models. I think this difference has something to do with what is wrong, without me knowing what is actually wrong.
Now, if I try to filter this:
products.customFilter({title: "MyTitle"});
That returns 0, even though I know there is one of that specific title.
Now the funky part. If I take the ENTIRE JSON and copy it, as in literally copy/paste it into the code like this:
var TestCollection = Backbone.Collection.extend({
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var testCollectionInstance = new TestCollection(COPY PASTED HUGE JSON DATA);
testCollectionInstance.customFilter({title: "MyTitle"});
Now that returns the 1 model which I was expecting. The difference when I log the two collections can be seen below. Is there some funky behaviour in the .fetch() I am unaware of?
Edit 2: It may also be of value that using the .fetch() I have no problems actually using the models in a view. It's only the filtering part which is funky.
Edit 3: Added the view. It may very well be that I just don't get the flow yet. Basically I had it all working when I only had to fetch() the data and send it to the view, however, the fetch was hardcoded into the render function, so this.fetch({success: send to template}); This may be wrong.
What I want to do is be able to filter the collection and send ANY collection to the render method and then render the template with that collection.
var ProductList = Backbone.View.extend({
el: '#page',
render: function(){
var that = this; /* save the reference to this for use in anonymous functions */
var template = _.template($('#product-list-template').html());
that.$el.html(template({ products: products.models }));
//before the fetch() call was here and then I rendered the template, however, I needed to get it out so I can update my collection and re-render with a new one (so it's not hard-coded to fetch so to speak)
},
events: {
'keyup #search' : 'search'
},
search : function (ev){
var letters = $("#search").val();
}
});
Edit: New image added to clearify the problem
It's a bit tricky, you need to understand how the console works.
Logging objects or arrays is not like logging primitive values like strings or numbers.
When you log an object to the console, you are logging the reference to that object in memory.
In the first log that object has no models but once the models are fetched the object gets updated (not what you have previously logged!) and now that same object has 6 models. It's the same object but the console prints the current value/properties.
To answer your question, IO is asynchronous. You need to wait for that objects to be fetched from the server. That's what events are for. fetch triggers a sync event. Model emits the sync when the fetch is completed.
So:
var Products = Backbone.Collection.extend({
model: Product,
url : 'restendpoint',
customFilter: function(f){
var results = this.where(f);
return new TestCollection(results);
}
});
var products = new Products();
products.fetch();
console.log(products.length); // 0
products.on('sync',function(){
console.log(products.length); // 6 or whatever
products.customFilter({title: 'MyTitle'});
})
It seems like a response to your ajax request hasn't been received yet by the time you run customFilter. You should be able to use the following to ensure that the request has finished.
var that = this;
this.fetch({
success: function () {
newCollection = that.customFilter({ title: 'foo' });
}
});

Saving a model in local storage

I'm using Jerome's localStorage adapter with Backbone and it works great for collections.
But, now I have a single model that I need to save. So in my model I set:
localStorage: new Store("msg")
I then do my saves and fetch. My problem is that everytime I do a refresh and initialize my app a new representation of my model is added to localStorage, see below.
What am I doing wrong?
window.localStorage.msg = {
// Created after first run
"1de5770c-1431-3b15-539b-695cedf3a415":{
"title":"First run",
"id":"1de5770c-1431-3b15-539b-695cedf3a415"
},
// Created after second run
"26c1fdb7-5803-a61f-ca12-2701dba9a09e":{
"0":{
"title":"First run",
"id":"1de5770c-1431-3b15-539b-695cedf3a415"
},
"title":"Second run",
"id":"26c1fdb7-5803-a61f-ca12-2701dba9a09e"
}
}
I ran into same issue. Maybe you have something similar to this
var Settings = Backbone.Model.extend({
localStorage: new Store("Settings"),
defaults: { a: 1 }
});
var s = new Settings;
s.fetch();
I changed to
var s = new Settings({ id: 1 });
localStorage adapter check for id like
case "read": resp = model.id ? store.find(model) : store.findAll(); break;
so 0 or "" for id wont work and it will return all models in one
I'm new to backbone.js too, but it looks like the persistence model is analogous to database tables. That is to say, it's designed to create/delete/read records from a table. The localStorage adapter does the same, so what you are doing there is creating a Msg "table"
in localStorage, and creating a new Msg "record" each time, and the adapter gives each new Msg a unique id.
If you just have one object, it's probably easier to just use localStorage directly. The API is really straight forward:
localStorage.setItem("key","value");
Keep in mind that localStorage only deals with key/value pairs as strings, so you'd need to convert to/from string format.
Take a look a this question for more on doing that:
Storing Objects in HTML5 localStorage

Categories

Resources