I'm trying to populate a collection of supermarket by adding the products to each supermarket, in an asynchronous way.
The objective is to pass from having something like this:
[{
name: 'supermarket x',
products: [1, 2]
}]
To something more like this:
[{
name: 'supermarket x',
products: [{
id: 1,
name: 'cacao'
}, {
id: 2,
name: 'milk'
}]
}]
I got to make the base code for this but I cannot achieve to populate the first stream with the second one once it's completed.
Any ideas?
I leave here a JSBin with the structure to make it faster for you
https://jsbin.com/nucutox/1/edit?js,console
Thanks!
So, you have a getSupermarkets() function which returns a stream that emits multiple supermarket objects and a getProduct(id) function which returns a stream that emits a single product with the specified id and then completes. And you want to map a stream of supermarkets containing product ids to a stream of supermarkets containing "resolved" product objects. Did I get this right?
Here's my solution:
const supermarkets$ = getSupermarkets()
.concatMap(supermarket => {
const productStreams = supermarket.products
.map(productId => getProduct(productId));
return Rx.Observable.combineLatest(productStreams)
.map(products => Object.assign({}, supermarket, { products }));
});
supermarkets$.subscribe(x => console.log(x));
When a new supermarket object arrives, we first map its array of product ids to an array of product observables, i.e. Array<Id> => Array<Observable<Product>>. Then we pass that array to a combineLatest operator which waits until each of the observables produces a value and emits an array of those values, i.e. an array of product objects. Then we produce a new supermarket object with products set to the array. We want to keep the original supermarket object unchanged, that's why Object.assign.
To make it work I had to slightly update your implementation of getProduct(): use .of(product) operator instead of .from(product) as product is a plain object we want to emit.
Related
How can we store an nested JSON object in SQLite database. In Android Room we used to have Embedded and Relation to store and retrieve complex data objects. But in flutter how can we achieve the same?
I tried exploring sqflite, floor, and moor. But none seem to help except for Moor, which allows us Map the values to Object using Joins. something like below code.
Stream<List<TaskWithTag>> watchAllTasks() {
return (select(tasks)
..orderBy(
[
(t) =>
OrderingTerm(expression: t.dueDate, mode: OrderingMode.desc),
(t) => OrderingTerm(expression: t.name),
],
))
.join(
[
leftOuterJoin(tags, tags.name.equalsExp(tasks.tagName)),
],
)
.watch()
.map((rows) => rows.map(
(row) {
return TaskWithTag(
task: row.readTable(tasks),
tag: row.readTable(tags),
);
},
).toList());
}
So What exactly is the right way to do this?
I want to render only one item from an array stored in redux.
For example, let's say my stored array is as follow:
[
{item1: 1},
{item2: 2}
]
I thought I could target item at index 0 like this:
const mapStateToProps = state => ({
userInput: state.userInput.datas[0]
});
However, it still returns the whole array.
It was simpler than I thought:
Instead of targeting an array item in mapStateToProps, i passed it as an argument when dispatching my thunk.
e.g:
this.props.dispatch(fetchSubGroup(this.props.userInput[0]));
I am trying to denormalize some data from the redux state, but I can't get it to work. I have tried putting together the denormalize function in all ways I can think. It is a guessing game and nothing have worked so far.
First I normalize the product data and put it into the state.
The entities are being put in state.data.products, including the products.
state.data: {
products: {
products: {
9: {
id: 9
attributes: [ 10, 45, 23 ],
things: [23, 55, 77]
},
11 //etc
}
attributes: {
10: {
id: 10
},
45: //etc
},
things: { //etc
}
}
So there is one property "products" that includes all entities, and there is another "products" for the "products" entity.
The normalizing works without problem:
normalize(data, [productSchema]);
The "productSchema" is defined like this:
productSchema = new schema.Entity('products', {
attributes: [attributeSchema],
things: [thingsSchema],
//+ other stuff
});
Now I want to denormalize a product, for example 9.
I use "connect" in a view component to call this function to retrieve the data from state and denormalize it:
export const denormalizeProduct = (state, productId) => {
const entities = state.data.products;
return denormalize(
productId,
[productSchema],
entities
);
}
If I call denormalizeProductById(state, 9) , I only get 9 back.
(similar to this question , but I couldn't find a solution from it, and it mixes together two examples)
If I put the "productSchema" without the array:
return denormalize(
productId,
productSchema,
entities
);
I get the error: TypeError: entities[schemaKey] is undefined.
Everything I have tried results in either "9" or the above error.
Edit:
found the error - added it below
I found the error - one of my schemas was wrong: I had created new schema.Entity('shippingZones') but in the reducer I added it to the state with a different name:
shipping_zones: action.payload.shippingZones
So it was looking for "shippingZones" while there was only "shipping_zones" I guess. So I changed it to new schema.Entity('shipping_zones') and shipping_zones: action.payload.shipping_zones
It's a pity that the message TypeError: entities[schemaKey] is undefined couldn't have specified "shippingZones" , then I could have saved a few days time.
So this code worked in the end:
const entities = state.data.products;
return denormalize(
productId,
productSchema,
entities
);
Following this question: Add data to http response using rxjs
I've tried to adapt this code to my use case where the result of the first http call yields an array instead of a value... but I can't get my head around it.
How do I write in rxjs (Typescript) the following pseudo code?
call my server
obtain an array of objects with the following properties: (external id, name)
for each object, call another server passing the external id
for each response from the external server, obtain another object and merge some of its properties into the object from my server with the same id
finally, subscribe and obtain an array of augmented objects with the following structure: (external id, name, augmented prop1, augmented prop2, ...)
So far the only thing I was able to do is:
this._appService
.getUserGames()
.subscribe(games => {
this._userGames = _.map(games, game => ({ id: game.id, externalGameId: game.externalGameId, name: game.name }));
_.forEach(this._userGames, game => {
this._externalService
.getExternalGameById(game.externalGameId)
.subscribe(externalThing => {
(<any>game).thumbnail = externalThing.thumbnail;
(<any>game).name = externalThing.name;
});
});
});
Thanks in advance
I found a way to make it work. I'll comment the code to better explain what it does, especially to myself :D
this._appService
.getUserGames() // Here we have an observable that emits only 1 value: an any[]
.mergeMap(games => _.map(games, game => this._externalService.augmentGame(game))) // Here we map the any[] to an Observable<any>[]. The external service takes the object and enriches it with more properties
.concatAll() // This will take n observables (the Observable<any>[]) and return an Observable<any> that emits n values
.toArray() // But I want a single emission of an any[], so I turn that n emissions to a single emission of an array
.subscribe(games => { ... }); // TA-DAAAAA!
Don't use subscribe. Use map instead.
Can't test it, but should look more like this:
this._appService
.getUserGames()
.map(games => {
this._userGames = _.map(games, game => ({ id: game.id, externalGameId: game.externalGameId, name: game.name }));
return this._userGames.map(game => { /* this should return an array of observables.. */
return this._externalService
.getExternalGameById(game.externalGameId)
.map(externalThing => {
(<any>game).thumbnail = externalThing.thumbnail;
(<any>game).name = externalThing.name;
return game;
});
});
})
.mergeAll()
.subscribe(xx => ...); // here you can subscribe..
I have a game built on Meteor framework. One game document is something like this:
{
...
participants : [
{
"name":"a",
"character":"fighter",
"weapon" : "sword"
},
{
"name":"b",
"character":"wizard",
"weapon" : "book"
},
...
],
...
}
I want Fighter character not to see the character of the "b" user. (and b character not to see the a's) There are about 10 fields like character and weapon and their value can change during the game so as the restrictions.
Right now I am using Session variables not to display that information. However, it is not a very safe idea. How can I subscribe/publish documents according to the values based on characters?
There are 2 possible solutions that come to mind:
1. Publishing all combinations for different field values and subscribing according to the current state of the user. However, I am using Iron Router's waitOn feature to load subscriptions before rendering the page. So I am not very confident that I can change subscriptions during the game. Also because it is a time-sensitive game, I guess changing subscriptions would take time during the game and corrupt the game pleasure.
My problem right now is the user typing
Collection.find({})
to the console and see fields of other users. If I change my collection name into something difficult to find, can somebody discover the collection name? I could not find a command to find collections on the client side.
The way this is usually solved in Meteor is by using two publications. If your game state is represented by a single document you may have problem implementing this easily, so for the sake of an example I will temporarily assume that you have a Participants collection in which you're storing the corresponding data.
So anyway, you should have one subscription with data available to all the players, e.g.
Meteor.publish('players', function (gameId) {
return Participants.find({ gameId: gameId }, { fields: {
// exclude the "character" field from the result
character: 0
}});
});
and another subscription for private player data:
Meteor.publish('myPrivateData', function (gameId) {
// NOTE: not excluding anything, because we are only
// publishing a single document here, whose owner
// is the current user ...
return Participants.find({
userId: this.userId,
gameId: gameId,
});
});
Now, on the client side, the only thing you need to do is subscribe to both datasets, so:
Meteor.subscribe('players', myGameId);
Meteor.subscribe('myPrivateData', myGameId);
Meteor will be clever enough to merge the incoming data into a single Participants collection, in which other players' documents will not contain the character field.
EDIT
If your fields visibility is going to change dynamically I suggest the following approach:
put all the restricted properties in a separated collection that tracks exactly who can view which field
on client side use observe to integrate that collection into your local player representation for easier access to the data
Data model
For example, the collection may look like this:
PlayerProperties = new Mongo.Collection('playerProperties');
/* schema:
userId : String
gameId : String
key : String
value : *
whoCanSee : [String]
*/
Publishing data
First you will need to expose own properties to each player
Meteor.publish('myProperties', function (gameId) {
return PlayerProperties.find({
userId: this.userId,
gameId: gameId
});
});
then the other players properties:
Meteor.publish('otherPlayersProperties', function (gameId) {
if (!this.userId) return [];
return PlayerProperties.find({
gameId: gameId,
whoCanSee: this.userId,
});
});
Now the only thing you need to do during the game is to make sure you add corresponding userId to the whoCanSee array as soon as the user gets ability to see that property.
Improvements
In order to keep your data in order I suggest having a client-side-only collection, e.g. IntegratedPlayerData, which you can use to arrange the player properties into some manageable structure:
var IntegratedPlayerData = new Mongo.Collection(null);
var cache = {};
PlayerProperties.find().observe({
added: function (doc) {
IntegratedPlayerData.upsert({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
changed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$set: _.object([ doc.key ], [ doc.value ])
});
},
removed: function (doc) {
IntegratedPlayerData.update({ _id : doc.userId }, {
$unset: _.object([ doc.key ], [ true ])
});
}
});
This data "integration" is only a draft and can be refined in many different ways. It could potentially be done on server-side with a custom publish method.