Imagine a database structured this way:
If I need to display only a few of this info, not all of them, which approach is better?
Approach 1
download the whole node with:
getInfo(userId){
firebase.database().ref("userinfo/" + userId)
.on("value", (info => {
//code here
}))
}
and, once I have the whole info, show only what I need
or
Approach 2
download only the nodes that I need, this would imply a few calls running at the same time, example:
getInfoByNode(userId){
firebase.database().ref("userinfo/" + userId + "/name")
.on("value", (name => {
//code here
}))
firebase.database().ref("userinfo/" + userId + "/email")
.on("value", (email=> {
//code here
}))
firebase.database().ref("userinfo/" + userId + "/phone")
.on("value", (phone=> {
//code here
}))
}
Related
Considering the following simplified data structure:
Teacher
{
id: number,
name: string
students?: Student[] // filled with an inner second request
}
Student
{
name: string
}
TeachersResult (response of the first api request with no students inside)
{
teachers: Teacher[]
pagesCount: number // metadata for the pagination
}
My main question is how use RxJS to fill the students-property for every teacher with these two api endpoints:
GET http://localhost:1337/api/teachers
GET http://localhost:1337/api/students/{teacherId}
The first idea was to start with something like this:
getTeachersWithStudents(): Observable<TeachersResult> {
return this.apiService.getTeachers().pipe(
concatMap(teachersResult => {
const studentsObservables$: Observable<Student[]>[] = [];
teachersResult.teachers.foreach(teacher =>
studentsObservables$.push(this.apiService.getStudents(teacher.id)));
// Add students to associated teacher here? Is forkJoin() the right choice?
});
);
}
It feels complicated for me to add every students-result from the second api request to the associated teacher. This is the end result for me I want to achieve:
{
teachers: [
{
id: 1,
name: 'Uncle Bob',
students: [
{ name: 'Alice' },
{ name: 'Caren' }
]
},
{
...
}
],
pagesCount: 42
}
You may try something like this
getTeachersWithStudents(): Observable<TeachersResult> {
return this.apiService.getTeachers().pipe(
// concatMap here is right since you want to continue only after upstream
// has notifies the list of teachers
concatMap(teachersResult => {
const teachers = teachersResult.teachers;
const secondCalls = teachers.map(teacher => {
return this.apiService.getStudents(teacher.id).pipe(
// set the students into the teacher object and return the teacher
// filled with the students
map(students => {
teacher.students = students;
return teacher
})
);
})
// now you have an array of Observables for the second calls, you
// can therefore use forkJoin to execute them in parallel
return forkJoin(secondCalls).pipe(
// add the page number
map(teachersWithStudents => {
return {
teachers: teachersWithStudents,
pagesCount: teachersResult.pagesCount
}
})
)
})
);
}
In this way you are executing all the calls to get the students concurrently.
If you want to limit the concurrency rate, then you can use mergeMap in a slightly more complex stream, something like this
getTeachersWithStudents(): Observable<TeachersResult> {
return this.apiService.getTeachers().pipe(
concatMap(teachersResult => {
// transform the teachers array into a stream of teachers
const teachers = teachersResult.teachers;
return from(teachers).pipe(
// here you use mergeMap with the rate of concurrency desired
// in this example I set it to 10, which means there will be at
// most 10 requests for students on flight at the same time
mergeMap(teacher => {
return this.apiService.getStudents(teacher.id).pipe(
// set the students into the teacher object and return the teacher
// filled with the students
map(students => {
teacher.students = students;
return teacher
})
)
}, 10),
// now we want to repack all the teachers in one array using toArray
toArray(),
// here we create the response desired and return it
map(teachersWithStudents => {
return {
teachers: teachersWithStudents,
pagesCount: teachersResult.pagesCount
}
})
)
})
);
}
This stackblitz shows an example of the above 2 implementations.
I'm having the dexie stores showed in the print screen below:
Dexie stores print screen
My goal is to update a dexie field row from a store without losing the rest of the data.
For example: when I edit and save the field "com_name" from the second row (key={2}) I want to update "com_name" only and not lose the rest of the properties, see first and the third row.
I already tried with collection.modify and table.update but both deleted the rest of the properties when used the code below:
dexieDB.table('company').where('dexieKey').equals('{1}')
//USING table.update
//.update(dexieRecord.dexiekey, {
// company: {
// com_name: "TOP SERVE 2"
// }
//})
.modify(
{
company:
{
com_name: TOP SERVE 2
}
}
)
.then(function (updated) {
if (updated)
console.log("Success.");
else
console.log("Nothing was updated.");
})
.catch(function (err) { console.log(err); });
Any idea how can I accomplish that?
Thanks
Alex
You where right to use Table.update or Collection.modify. They should never delete other properties than the ones specified. Can you paste a jsitor.com or jsfiddle repro of that and someone may help you pinpoint why the code doesn't work as expected.
Now that you are saying I realised that company and contact stores are created dynamically and editedRecords store has the indexes explicitly declared therefore when update company or contact store, since dexie doesn't see the indexes will overwrite. I haven't tested it yet but I suspect this is the behaviour.
See the print screen below:
Dexie stores overview
Basically I have json raw data from db and in the browser I create the stores and stores data based on it, see code below:
function createDexieTables(jsonData) { //jsonData - array, is the json from db
const stores = {};
const editedRecordsTable = 'editedRecords';
jsonData.forEach((jsonPackage) => {
for (table in jsonPackage) {
if (_.find(dexieDB.tables, { 'name': table }) == undefined) {
stores[table] = 'dexieKey';
}
}
});
stores[editedRecordsTable] = 'dexieKey, table';
addDataToDexie(stores, jsonData);
}
function addDataToDexie(stores, jsonData) {
dbv1 = dexieDB.version(1);
if (jsonData.length > 0) {
dbv1.stores(stores);
jsonData.forEach((jsonPackage) => {
for (table in jsonPackage) {
jsonPackage[table].forEach((tableRow) => {
dexieDB.table(table).add(tableRow)
.then(function () {
console.log(tableRow, ' added to dexie db.');
})
.catch(function () {
console.log(tableRow, ' already exists.');
});
});
}
});
}
}
This is the json, which I convert to object and save to dexie in the value column and the key si "dexieKey":
[
{
"company": [
{
"dexieKey": "{1}",
"company": {
"com_pk": 1,
"com_name": "CloudFire",
"com_city": "Round Rock",
"serverLastEdit": [
{
"com_pk": "2021-06-02T11:30:24.774Z"
},
{
"com_name": "2021-06-02T11:30:24.774Z"
},
{
"com_city": "2021-06-02T11:30:24.774Z"
}
],
"userLastEdit": []
}
}
]
}
]
Any idea why indexes were not populated when generating them dynamically?
Given the JSON data, i understand what's going wrong.
Instead of passing the following to update():
{
company:
{
com_name: "TOP SERVE 2"
}
}
You probably meant to pass this:
{
"company.com_name": "TOP SERVE 2"
}
Another hint is to do the add within an rw transaction, or even better if you can use bulkAdd() instead to optimize the performance.
This is a pretty simple question.
How to implement subscriptions in graphql?
I'm asking specifically for when using graphql.js constructors like below ?
I could not find a clean/simple implementation.
There is another question here, but it deals with relay.js - i don't want to unnecessarily increase the nr of external dependencies in my app.
What i have:
module.exports = function (database){
return new GraphQLSchema(
{ query: RootQuery(database)
, mutation: RootMutation(database)
, subscription: RootSubscription(database) -- i can see this in graphiql - see below
}
);
}
function RootSubscription(database){
return new GraphQLObjectType(
{ name: "RootSubscriptionType"
, fields:
{ getCounterEvery2Seconds:
{ type: new GraphQLNonNull(GraphQLInt)
, args :
{ id: { type: GraphQLString }
}
, subscribe(parent, args, context){
// this subscribe function is never called .. why?
const iterator = simpleIterator()
return iterator
}
}
}
}
)
}
I learned that i need a subscribe() which must return an iterator from this github issue.
And here is a simple async iterator. All this iterator does - is to increase and return the counter every 2 seconds. When it reaches 10 it stops.
function simpleIterator(){
return {
[ Symbol.asyncIterator ]: () => {
let i = 0
return {
next: async function(){
i++
await delay(2000)
if(i > 10){
return { done: true }
}
return {
value: i,
done: false
}
}
}
}
}
}
When i run the graphiql subscription, it returns null for some reason:
I'm piecing together code from multiple sources - wasting time and hacking it basically. Can you help me figure this one out?
Subscriptions are such a big feature, where are they properly documented? Where is that snippet of code which you just copy paste - like queries are for example - look here.
Also, i can't use an example where the schema is separate - as a string/from a file. I already created my schema as javascript constructors. Now since im trying to add subscriptions i can't just move back to using a schema as a string. Requires rewriting the entire project. Or can i actually have both? Thanks :)
I'm trying to build a query with the GitHub API v4 (GraphQL) to get the number of contributors.
At the moment I have something of the likes of
query ($owner: String!, $name: String!) {
repository(owner: $owner, name: $name) {
ref(qualifiedName: "master") {
target {
... on Commit {
history(first: 100) {
nodes {
author {
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
}
}
}
where I'm going through all the commits and get the name of the Authors (at the time I was trying to get the number of commits for contributor), but for repositories with a large amount of commits, this takes a lot of time.
So back to my question, is the a way to get only the number of contributors in a repository?
As far as I know, this is only possible with the REST API (v3), by requesting only one item per page, and extracting the total number of pages in the Response headers.
With this graphql query you can get the:
- total releases count
- total branches count
- total commits count
query{
repository(owner:"kasadawa", name:"vmware-task") {
Releases:refs(first: 0, refPrefix: "refs/tags/") {
totalCount
}
Branches:refs(first: 0, refPrefix: "refs/heads/") {
totalCount
}
object(expression:"master") {
... on Commit {
history {
totalCount
}
}
}
}
}
But if you want to get the contributors, you should do it with the REST API, because currently there is no simple way to implement it with GRAPHQL API.
Here is a solutions with the REST API.
const options = {
url: 'https://api.github.com/repos/vmware/contributors' ,
'json': true ,
headers: {
'User-Agent':'request',
'Authorization': 'token API_KEY_GENERATED_FROM_GITHUB'
}
};
var lastPageNum = 0 ; // global variable
request.getAsync(options).then((res) =>{
this.checkHeaders(res.headers.status) // just check the header status
// check if there are more pages
if(!!res.headers.link){
const [ , lastURL] = res.headers.link.split(','); // getting the last page link from the header
lastPageNum = +lastURL.match(/page=(\d+)/)[1]; // get the number from the url string
options.url = licenseUrl + lastPageNum;
return request.getAsync(options) // make Request with the last page, in order to get the last page results, they could be less than 30
}else{
// if its single page just resolve on to the chain
return Promise.resolve(res.body.length);
}
})
.then((lastPageRes)=>{
return (lastPageNum !== 0
? ( (lastPageNum-1)*30 + lastPageRes.body.length )
: lastPageRes)
})
.catch() // handle errors
Checkout for updates: https://github.community/t5/GitHub-API-Development-and/Get-contributor-count-with-the-graphql-api/td-p/18593
I'm working on an app with Meteor and React.
I'm trying to have the content of inventories_array from an external MongoDB database, but it's extremely slow. I wait 7 seconds, I have one object, I wait 5 seconds, two other objects, etc...
Spaces = new Mongo.Collection("Space");
Properties = new Mongo.Collection("Property");
Inventories = new Mongo.Collection("Inventory");
if (Meteor.isClient) {
Meteor.subscribe("Property");
Meteor.subscribe("Space");
Meteor.subscribe("Inventory");
Tracker.autorun(function() {
inventories_array = Inventories.find({quantityBooked: 2},{fields: {priceTaxExcl: 1, property: 1, space: 1}}).fetch();
console.log(inventories_array);
}
if (Meteor.isServer) {
Meteor.publish("Property", function () {
return Properties.find();
});
Meteor.publish("Space", function () {
return Spaces.find();
});
Meteor.publish("Inventory", function () {
return Inventories.find();
});
}
The Inventory Object:
{
...
"property" : ObjectId("..."),
"space" : ObjectId("..."),
"quantityBooked":2,
"priceTaxExcl":...,
...
}
I launch the app with MONGO_URL=mongodb://127.0.0.1:27017/mydb meteor run
Any ideas why it's so slow?
If you look at your network tab in the inspector you'll see all the data flowing from the server to the client for each subscription and you'll be able to judge both how large it is and how long it takes.
I'd recommend at a first step that you alter your Inventory publication as follows:
Meteor.publish("Inventory", function () {
if ( this.userId ){ // only return data if we have a logged-in user
return Inventories.find({quantityBooked: 2},{fields: {priceTaxExcl: 1, property: 1, space: 1}});
} else {
this.ready();
}
});
This way your server is only sending the required fields from the required documents (assuming you only want docs where {quantityBooked: 2}.