Buffer grouped observables - reactive-programming

I have the following class and array
class Hero {
id: number;
name: string;
}
const HEROES: Hero[] = [
{ id: 11, name: 'Narco' },
{ id: 12, name: 'Narco' },
{ id: 13, name: 'Bombasto' },
{ id: 14, name: 'Bombasto' },
{ id: 15, name: 'Bombasto' },
{ id: 16, name: 'Dynama' },
{ id: 17, name: 'Dynama' },
{ id: 18, name: 'Dynama' },
{ id: 19, name: 'Dynama' },
{ id: 20, name: 'Dynama' }
];
I want to create an observable that treats the HEROES array as the source, groups the array by name and emits the result as a single array, i.e., I should end up with three arrays, one for Narco, one for Bombasto and one for Dynama.
I can create my source observable as follows
var heroSource = Observable.create((observer:any) => {
HEROES.forEach((hero: Hero) => {
observer.next(hero);
})
});
I can then group heros by using groupBy, i.e.,
var groupHeroSource = heroSource
.groupBy((hero: Hero) => {return hero.name});
This effectively gives me as many observables as there are different names in my HEROES array (three in this case). However, these will be emitted as a sequence and ideally I'd like to buffer them until heroSource is complete. How would I use the buffer operator in rxjs on my grouped observables so that emit a single collection?

First of all you can create your initial observable much simpler:
var heroSource = Observable.from(HEROES);
Next, your groupBy mapper/selector can be abbreviated to:
var groupHeroSource = heroSource.groupBy((hero: Hero): string => hero.name);
To solve the original problem I needed to buffer the streams, since they are ready a buffer with time of 0 would do the work (I guess there should be a more elegant solution out there), use take(1) to take only the first result (and avoid a repeating buffer) and then merge all:
var finalGroup = groupHeroSource.map((ob) => ob.bufferWithTime(0).take(1)).mergeAll();
Note that since that since your array is actually static, putting it through a stream and then mapping it might not be the simplest solution, you can simply reduce it:
var grouped = HEROES.reduce((acc: any, hero: Hero) => {
acc[hero.name] = acc[hero.name] || [];
acc[hero.name].push(hero);
return acc;
}, {});
Since Object.values is not standard, you'll have to iterate the keys to get an array, yet, it might be a better fit for your need

Related

BelongsTo in Sails.js

Hello, I need your help please with 2 questions.
I have 2 Models
One to Many
(One) Customer{ id, names, dni} -> Invoice {id, date, ....customer_id} (Many)
1. How can I get this?
I need to consume the api "GET /api/invoices" and that the json return of this, in turn, returns an array
[{
id: 1,
date: '2022-01-01',
....invoice
customer: {
dni: 1,
names: 'Example'
}
},
{
id: 2,
date: '2022-01-02',
....invoice
customer: {
dni: 2,
names: 'Example 2'
}
},
]
So far what I have found in the sailsjs documentation are only examples with POPULATE, where they only show how to list the User model with its corresponding created ones (hasMany)
//var users = await User.find().populate('pets');
// The users object would look something like the following
// [{
// id: 123,
// firstName: 'Foo',
// lastName: 'Bar',
// pets: [{
// id: 1,
// breed: 'labrador',
// type: 'dog',
// name: 'fido',
// user: 123
// }]
// }]
//---This is not what I need.
Is there a function or configuration that I have not found?
Or would I do something like this?
Invoices.find().exec(async(err, invoices)=>{
if(invoices){
for(i = 0; i< invoices.length; i++){
const customer = await Customer.find({id: invoices[i].customer_id});
invoices[i].customer = customer;
}
});
The point is that this takes much longer than doing a query with join
const invoices = await sails.sendNativeQuery('SELECT * from INVOICE A A inner join CUSTOMER B on A.customer_id=B.id ', []);
But I don't know how to get a JSON with the previous structure if I do it by query
2. What is the best option that can solve my problem?
The populate method works in both directions: oneToMany, manyToMany, and manyToOne:
https://sailsjs.com/documentation/reference/waterline-orm/queries/populate
If any condition is required, you could check the details on the section Populating a collection association:
var usersNamedFinn = await User.find({ name:'Finn' })
.populate('currentSwords', {
where: {
color: 'purple'
},
limit: 3,
sort: 'hipness DESC'
});

b-form-select get selected option as value

I'm having some trouble understanding b-form-select from bootstrap-vue.
I have a list of object lets say
factories = [{ id: 1, name: "A" }, { id: 2, name: "B" }]`
And my select as
<b-form-select
v-model="factory"
:options="factories"
value-field="id"
text-field="name"
/>
But how would I do to get the full selected object rather than just the id without having to declare options manually ?
This works but it feels 'hacky'.
<b-form-select v-model="factory">
<option v-for="f in factories" :value="f" :key="f.id">{{f.name}}</option>
</b-form-select>
If not possible, any reasons why ?
This doesn't work because <b-form-select> requires the options array to take the form of:
[{value: factory, text: factory.name}]
Put another way, <b-form-select> doesn't know what to do with [{id: 1, name: 'A'}]
To make your first form work, you'll need to transform the array 'factories' into an array the can use:
const factories = [{ id: 1, name: "A" }, { id: 2, name: "B" }]
var factoriesSelectList = []
factories.forEach((factory, index, mechanicsArray) => {
var selectListOption = {
value: factory,
text: factory.name
}
factoriesSelectList.push(selectListOption)
})
Then, in your template:
<b-form-select
v-model="factory"
:options="factoriesSelectList"
/>
Note: This probably isn't much different than doing it in the template as in your second form. I've not looked, but I'm betting the resulting javascript is similar.

Implementing pagination in vanilla GraphQL

Every tutorial I have found thus far has achieved pagination in GraphQL via Apollo, Relay, or some other magic framework. I was hoping to find answers in similar asked questions here but they don't exist. I understand how to setup the queries but I'm unclear as to how I would implement the resolvers.
Could someone point me in the right direction? I am using mongoose/MongoDB and ES5, if that helps.
EDIT: It's worth noting that the official site for learning GraphQL doesn't have an entry on pagination if you choose to use graphql.js.
EDIT 2: I love that there are some people who vote to close questions before doing their research whereas others use their knowledge to help others. You can't stop progress, no matter how hard you try. (:
Pagination in vanilla GraphQL
// Pagination argument type to represent offset and limit arguments
const PaginationArgType = new GraphQLInputObjectType({
name: 'PaginationArg',
fields: {
offset: {
type: GraphQLInt,
description: "Skip n rows."
},
first: {
type: GraphQLInt,
description: "First n rows after the offset."
},
}
})
// Function to generate paginated list type for a GraphQLObjectType (for representing paginated response)
// Accepts a GraphQLObjectType as an argument and gives a paginated list type to represent paginated response.
const PaginatedListType = (ItemType) => new GraphQLObjectType({
name: 'Paginated' + ItemType, // So that a new type name is generated for each item type, when we want paginated types for different types (eg. for Person, Book, etc.). Otherwise, GraphQL would complain saying that duplicate type is created when there are multiple paginated types.
fields: {
count: { type: GraphQLInt },
items: { type: new GraphQLList(ItemType) }
}
})
// Type for representing a single item. eg. Person
const PersonType = new GraphQLObjectType({
name: 'Person',
fields: {
id: { type: new GraphQLNonNull(GraphQLID) },
name: { type: GraphQLString },
}
})
// Query type which accepts pagination arguments with resolve function
const PersonQueryTypes = {
people: {
type: PaginatedListType(PersonType),
args: {
pagination: {
type: PaginationArgType,
defaultValue: { offset: 0, first: 10 }
},
},
resolve: (_, args) => {
const { offset, first } = args.pagination
// Call MongoDB/Mongoose functions to fetch data and count from database here.
return {
items: People.find().skip(offset).limit(first).exec()
count: People.count()
}
},
}
}
// Root query type
const QueryType = new GraphQLObjectType({
name: 'QueryType',
fields: {
...PersonQueryTypes,
},
});
// GraphQL Schema
const Schema = new GraphQLSchema({
query: QueryType
});
and when querying:
{
people(pagination: {offset: 0, first: 10}) {
items {
id
name
}
count
}
}
Have created a launchpad here.
There's a number of ways you could implement pagination, but here's two simple example resolvers that use Mongoose to get you started:
Simple pagination using limit and skip:
(obj, { pageSize = 10, page = 0 }) => {
return Foo.find()
.skip(page*pageSize)
.limit(pageSize)
.exec()
}
Using _id as a cursor:
(obj, { pageSize = 10, cursor }) => {
const params = cursor ? {'_id': {'$gt': cursor}} : undefined
return Foo.find(params).limit(pageSize).exec()
}

Using search with Bloodhound

I am trying to find out how Bloodhound works (without typeahead).
var engine = new Bloodhound({
local: [{ id: 1, name: 'dog' }, { id: 2, name: 'pig' }],
identify: function(obj) { return obj.id; },
queryTokenizer: Bloodhound.tokenizers.whitespace,
datumTokenizer: Bloodhound.tokenizers.whitespace
});
engine.search('do', function(datums) {
console.log(datums); // results: []
});
In this very basic example, why does my search not return my first item? What I am doing wrong?
Out of the box, Bloodhound tokenizers work for an array of a basic type. You have "complex" data (an object with 2 properties), so you must tell Bloodhoud what to tokenize using the obj tokenizer and passing property names:
datumTokenizer: Bloodhound.tokenizers.obj.whitespace("id", "name"),

Rx (RxJS) group Observable stream according to an array of keys

I am using RxJS to group the results of an AJAX API call (Google Knowledge Graph) according to a type key. Each result item may contain multiple types. The results look like this:
const data = [
{ name: 'item1', types:['A', 'B']},
{ name: 'item2', types:['C', 'D']},
{ name: 'item3', types:['A', 'B', 'C']},
{ name: 'item4', types:['B']},
{ name: 'item5', types:['B', 'C']},
{ name: 'item6', types:['A', 'D']},
{ name: 'item7', types:['B', 'C']},
];
My approach to achieving this is to flatten the results so that each element contains one type key only to be able to use the built-in operator groupBy.
Rx.Observable.from(data)
.flatMap(item => {
return item.types
.map(type => Object.assign({}, item, {type}) );
})
.groupBy(item => item.type)
.subscribe(type => {
type.count().subscribe(count => console.log(`${count} items has type ${type.key}`));
});
The results will transform into something like:
{ name: 'item1', type:'A'},
{ name: 'item1', type:'B'},
{ name: 'item2', type:'c'},
...
And the output will be:
3 items has type A
5 items has type B
4 items has type C
2 items has type D
This achieves the desired behavior, but I am not sure if duplicating the items is the most efficient approach to use with a live stream of large data before completion. I also assume that this is a common use case so I wonder if there is another built-in operator to achieve this that I missed in the API doc. I'd love to get some input or suggestions.
The above example is available at https://jsfiddle.net/t9Ls1p2f/