Merge 2 arrays of objects subdocs based on id field - rethinkdb

I have a M-N relation of users and teams & I'm trying to use a subdoc strategy instead of a standard 3-table SQL strategy.
user = {
id: 'user1',
teams: [{'team1', roles: ['admin']}, {'team2', roles: ['editor']}]
}
team = {
id: 'team1',
name: 'The Team #1'
}
I'd like to grab the name field from the team table and stick it in the appropriate subdoc:
query = {
id: 'user1',
teams: [{'team1', roles: ['admin'], name: 'The Team #1'}, {'team2', roles: ['editor'], name: 'The Team #2'}]
}
I can get the team doc easily enough, but I keep overwriting the teams array:
//Query
r.table('users').get('user1').merge(function(user) {
return {
teams: r.table('teams').getAll(r.args(user('teams')
.map(function(team) {return team('id')}))).coerceTo('array')
}
})
//Bad result
user.teams = [
{
"id": "team1" ,
"name": "team 1"
} ,
{
"id": "team2" ,
"name": "team 2"
}
]
Is it possible to merge an array of objects based on an object field, or should I do this at the application level? Is there a better way?

If the teams array has the ID of the team, you can do something like this:
r.table('users').get('user1').merge(function(user) {
return {teams: user('teams').merge(function(team) {
return r.table('teams').get(team('id'));
})};
})

Related

How to return complex object as scalar type in GraphQL?

Let's imagine we have GraphQL API that can return an object Entity with Id and Name properties and I requested Name only:
query {
entities {
name
}
}
And it returns
{
"data": {
"entities": [
{
"name": "Name1"
},
{
"name": "Name2"
}
]
}
}
But what if I want to have only the name of entities as a scalar type? In other words, I want to have something like:
{
"data": {
"entities": [
"Name1",
"Name2"
]
}
}
Is it possible to have such result without changes on the GraphQL API side? Aliases, Fragments, etc. GraphQL has a lot of built-in query capabilities, but none of the known me can return complex objects as scalar type.
what you're asking for is almost impossible if you don't want to change the type definition for Entities.
This: 👇🏽
Entity: id: int! name: String
entities(): [Entity]
returns an array of objects with keys name and id.
To achieve what you're asking you either change Entity to be just a string or have your client reduce that object to an array of just Entity names when they receive it.
They could do something like this:
const data = {
entities: [
{
name: 'Name1',
},
{
name: 'Name2',
},
],
};
const entityNames = data.entities.reduce(
(acc, curr) => [...acc, curr.name],
[]
);
console.log(entityNames);

GraphQL - Relationship returning null

I started to learn GraphQL and I'm trying to create the following relationship:
type User {
id: ID!,
name: String!,
favoriteFoods: [Food]
}
type Food {
id: ID!
name: String!
recipe: String
}
So basically, a user can have many favorite foods, and a food can be the favorite of many users. I'm using graphql.js, here's my code:
const Person = new GraphQLObjectType({
name: 'Person',
description: 'Represents a Person type',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
favoriteFoods: {type: GraphQLList(Food)},
})
})
const Food = new GraphQLObjectType({
name: 'Food',
description: 'Favorite food(s) of a person',
fields: () => ({
id: {type: GraphQLNonNull(GraphQLID)},
name: {type: GraphQLNonNull(GraphQLString)},
recipe: {type: GraphQLString}
})
})
And here's the food data:
let foodData = [
{id: 1, name: 'Lasagna', recipe: 'Do this then that then put it in the oven'},
{id: 2, name: 'Pancakes', recipe: 'If you stop to think about, it\'s just a thin, tasteless cake.'},
{id: 3, name: 'Cereal', recipe: 'The universal "I\'m not in the mood to cook." recipe.'},
{id: 4, name: 'Hashbrowns', recipe: 'Just a potato and an oil and you\'re all set.'}
]
Since I'm just trying things out yet, my resolver basically just returns a user that is created inside the resolver itself. My thought process was: put the food IDs in a GraphQLList, then get the data from foodData usind lodash function find(), and replace the values in person.favoriteFoods with the data found.
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
description: 'Root Query',
fields: {
person: {
type: Person,
resolve(parent) {
let person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3]
}
foodIds = person.favoriteFoods
for (var i = 0; i < foodIds.length; i++) {
person.favoriteFoods.push(_.find(foodData, {id: foodIds[i]}))
person.favoriteFoods.shift()
}
return person
}
}
}
})
But the last food is returning null. Here's the result of a query:
query {
person {
name
favoriteFoods {
name
recipe
}
}
}
# Returns
{
"data": {
"person": {
"name": "Daniel",
"favoriteFoods": [
{
"name": "Lasagna",
"recipe": "Do this then that then put it in the oven"
},
{
"name": "Pancakes",
"recipe": "If you stop to think about, it's just a thin, tasteless cake."
},
null
]
}
}
}
Is it even possible to return the data from the Food type by using only its ID? Or should I make another query just for that? In my head the relationship makes sense, I don't think I need to store the IDs of all the users that like a certain food in the foodData since it has an ID that I can use to fetch the data, so I can't see the problem with the code or its structure.
Calling shift and push on an array while iterating through that same array will invariably lead to some unexpected results. You could make a copy of the array, but it'd be much easier to just use map:
const person = {
name: 'Daniel',
favoriteFoods: [1, 2, 3],
}
person.favoriteFoods = person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
return person
The other issue here is that if your schema returns a Person in another resolver, you'll have to duplicate this logic in that resolver too. What you really should do is just return the person with favoriteFoods: [1, 2, 3]. Then write a separate resolver for the favoriteFoods field:
resolve(person) {
return person.favoriteFoods.map(id => {
return foodData.find(food => food.id === id)
})
}

How can i use logical operators with strapi's graphql filtering

I'm passing graphql variables like this:
variables: {
where: {
league: {
name_contains: "some league name"
},
teams: {
name_contains: "some teams name"
}
}
}
I want either the league's name or teams name or both to exist.
but my current configuration is set to have both league's name and team's name to exist
the query looks like this:
const SEARCH_QUERY = gql`
query SEARCH_QUERY($where: JSON) {
games(where: $where) {
id
teams {
id
name
}
league {
id
name
}
}
}
I guess that you are looking some thing like in the howtographql documentation, I can't see on the moment some similar solution in the strapi documentation but I have some another workaround solution...
teams.name && league.name
You need just these arguments
where:{teams: {name_contains: "some league name"}, league: {name_contains: "some league name"}}
teams.name || league.name
In this case you need merge separate queries for each name parent like this:
where:{teams: {name_contains: "some league name"}}
where:{league: {name_contains: "some league name"}}
Body transform function:
const gameMap = [...teamsGames, ...leagueGames].reduce((games, game) => {
games[game.id] = games[game.id] ? games[game.id] : { id: game.id, teams: [], league: [] };
games[game.id].teams = [...games[game.id].teams, ...game.teams];
games[game.id].league = [...games[game.id].league, ...game.league];
return games;
}, {});
//flat to array
Object.keys(gameMap).map(key => gameMap[key]);

What does a GraphQL resolver return for an object with a relation?

If I have
type Post {
title: String!
body: String!
author: User!
}
type User {
name: String
posts: [Post!]!
}
type Query {
users: [User!]!
}
What does the resolver users return? I understand it can return an empty list, but when it returns a populated list I'm not sure how to handle that. Intuitively, it seems to me like it should be infinite due to the user containing a list of their posts, which will each contain the user, etc. Clearly this cannot be the case, so what does the resolver for Query.users return?
The resolver returns only what you make it return. It's not automatically generated based on the types. What it does is: it matched the types of the object according to what you make it resolve.
In this case your resolver would return a list of users, and each user has a name and a list of posts, and each post has a title, body and an author.
And yes, it seems that it can create an infinite data stream, but that's exactly the idea behind graph, is that all the data is connected to it's relations and vice-versa.
You can imagine the return data something like this:
data: {
users: [
{
name: "user 1",
posts: [
{
title: "post 1",
body: "post body",
author: {
name: "user 1",
posts: [
{
title: "post 1",
body: "post body",
author: {
name: "user 1",
posts: [
{
.....
}
]
}
}
]
}
}
]
},
{
name: "user 2",
posts: [
{
title: "post 2",
body: "post body",
author: {
name: "user 2",
posts: [
{
title: "post 2",
body: "post body",
author: {
name: "user 2",
posts: [
{
.....
}
]
}
}
]
}
}
]
}
]
}
And again, GraphQL only returns the data you ask it to return. So, if you only want users and posts the query would be something like:
query getUsers {
users {
name
posts {
title
body
}
}
}
In this case it would only return the users and their posts, not the authors.

GraphQL multiple queries in one request

I have some groups created by users in my database. Each group has multiple members in it. This is what a typical group looks like:
{
"groupName": "some-group-name",
"userIds": ["001", "002", "003"]
}
I also have a separate node of users in my database. This is what a user looks like:
{
"id": "001",
"userName": "some-unique-username"
}
I want to list every single group and its members. So, it would be:
group1
user1 id; user1 name;
user2 id; user2 name;
group2
user3 id; user3 name;
user4 id; user4 name;
and so on...
How would it be possible (if at all) to fetch these groups and their user ids and also query for users by id and return a single JSON containing all the data only with one request?
Right now I'm just fetching some amount of groups and iterating over the userIds of each of them and sending a request for each user separately.
I'm not asking about pagination or simple querying, I just want to get every group and the users inside those groups.
Figured it out myself.
What I needed to do was to create a separate resolver for fetching members of each group:
resolvers: {
...
Group: {
members: (group, _, context) => group.members.map(memberId => context.db.findUserById(memberId)
}
...
}
The parent group object is passed as the first argument to the resolver so when I query:
{
groups(count: 10){
name
members {
name
}
}
}
It returns the names of the first 10 groups along with its members' names.
With the following schema,
type User {
name: String
firstName: String
location: String
}
type Group {
groupname:String!
users:[User!]
}
type Query {
groups:[Group!]
group(name: String): Group
}
the following query:
{
groups
{
groupname
users {
name
firstName
location
}
}
}
could return
{
{ name: "group1",
users: [{name:"user1", firstName:"firstName1", location:"Localtion1"},
{name:"user2", firstName:"firstName2", location:"Localtion2"}]
},
{ name: "group2",
users: [{name:"user1", firstName:"firstName1", location:"Localtion1"}]
},
...etc..
}
Hope this helps,

Resources