GraphQL Resolver for Nested Array of Objects - schema

I am trying to get to grips with the GraphQL schema language as part of a work project. I'm running into problems I think because I'm not sure how to write the resolver for a particular data structure I need to use. I have some data for an appointment management system I am writing. There are at this point three collections - Staff, Account, and Booking. A simplified version of the typical json data looks like this:
Staff: {
id: 0,
Name: 'Joe Blogs',
Speciality: 'Fitter'
}
Account: {
id: 0,
Name: 'Fred Foo',
Company: 'Acme Holdings',
staffId: 0
}
Booking: {
id: 0,
Date: '2018-03-23',
staffId: 0,
accountId: 0,
AssignedStaff: [
{id: 0, staffId: 1},
{id: 1, staffId: 3}
]
}
So, a booking will relate to a particular account and be owned by a particular staff member. It will also have an arbitrary number of other staff assigned to it. My GraphQL schema looks like this:
type Staff {
id: Int!
Name: String!
Speciality: String!
accounts: [Account]
bookings: [Booking]
}
type Account {
id: Int!
Name: String!
Company: String!
staff: Staff!
bookings: [Booking]
}
type Booking {
id: Int!
Date: String!
staff: Staff!
account: Account!
AssignedStaff: [AssignedStaff]
}
type AssignedStaff {
id: Int!
staff: Staff!
}
I have working resolvers for the relationships between Staff and Account and Booking working properly. Where I am having problems is with writing a resolver for the assigned staff on a booking. I am doing the development using static dummy data for the time being. The working resolver functions look like this:
Account: {
bookings (account) {
return booking.filter(booking => booking.accountId === account.id)
},
staff (account) {
return staff.find(staff => staff.id === account.staffId)
}
},
Booking: {
account (booking) {
return account.find(account => account.id === booking.accountId)
},
staff (booking) {
return staff.find(staff => staff.id === booking.staffId)
}
},
Staff: {
accounts (staff) {
return account.filter(account => account.staffId === staff.id)
},
bookings (staff) {
return booking.filter(booking => booking.staffId === staff.id)
}
}
and they work fine. I can do e.g. a query like this:
staff(id: 0){
Name
accounts {
Name
bookings {
Date
}
}
}
and it works, the data returned is exactly what I expect. The problem arises when I try to return data about the assigned staff on a booking. I have a resolver for AssignedStaff which currently looks like this:
AssignedPhotographer: {
staff(booking) {
return staff.filter(staff => staff.id === booking.AssignedStaff.staffId)
}
}
but when I run a query against it like this for instance:
staff(id: 0){
Name
accounts {
Name
bookings {
Date
AssignedStaff {
staff{
Name
}
}
}
}
}
The data I get back has an array of null values for AssignedStaff. The number of nulls matches the number of assigned staff for the booking, but it seemingly is not linking to the Staff type properly. If I just request the ids of the assigned staff for a booking like this:
staff(id: 0){
Name
accounts {
Name
bookings {
Date
AssignedStaff {
id
}
}
}
}
It works ok and gives me back the correct data. I'm guessing that the problem is with the way I have done my resolvers, probably specifically with the AssignedStaff resolver. Can anybody please tell me what I'm doing wrong?

You have any array of Staff objects that you filter and then try to return as an array of AssignedStaff. Staff and AssignedStaff do not share any fields except for id -- which is why you see the id field populated but not the staff field.
GraphQL is expecting an array like this:
[ { id, staff } ]
and you are passing it
[ { id, name, speciality } ]
You'll need to modify your resolver to look something like this:
return staff
.filter(s => s.id === booking.AssignedStaff.staffId)
.map(f => ({ id: f.id, staff: f }))
I don't know what (if any) the intended difference between AssignedStaff.id and AssignedStaff.staff.id is so you may need to fiddle with the above code but it should give you a starting point.

Related

TypeORM select data from nested relations

Using
await this.budgetRepository.createQueryBuilder("budget")
.leftJoinAndSelect("budget.contact", "contact")
.leftJoinAndSelect("contact.photo", "contactPhoto")
.getMany();
I get a list with objects like this:
Budget {
id: 1,
unnecessary_property1: something,
contact: Contact {
unnecessary_property2: something,
photo: Photo {
unnecessary_property3: something,
url: "url.com"
},
},
}
But I want to select only the necessary properties in the nested objects (relations) and get a list of objects like this:
Budget {
id: 1,
contact: Contact {
photo: Photo {
url: "url.com"
},
},
}
How is that possible with TypeORM?
This is possible but we have to select everything manually with .select()
await this.budgetRepository.createQueryBuilder("budget")
.leftJoinAndSelect("budget.contact", "contact")
.leftJoinAndSelect("contact.photo", "contactPhoto")
.select(['budget.id', 'contactPhoto.url']
.getMany();
If you're using repository pattern that you will be achieve the similar result with:
await this.budgetRepository.find({
relations: ["contact", "contact.photo"]
})
You would have to use the .select() function and pass the given properties you want for each entity.
for your example:
const user = await createQueryBuilder("budget")
.leftJoinAndSelect("budget.contact", "contact")
.leftJoinAndSelect("contact.photo", "contactPhoto")
.select([/* everything from budget */, 'contact.photo.url'....]) // added selection
.getMany();

typeorm query in object list

I have an entity House with this column:
#ManyToMany(type => Person, Persons => Persons.Houses)
persons: Persons[];
And now i want to get all Houses that connected to a person.id:
this.houseRepository.find({
where: {
Persons: { ?? } // Here I cant do 'Persons: { id: idToFind }'
}
})
How can I query for this?
try this
query.leftJoinAndSelect('houses.persons', 'persons')
.where("persons.id = :personId", {personId: 1 }).getMany();

Vue.js filter values from two store

I need help with getters in Vue.js, I'm trying to get list of data that is connected with id in two different stores.
Here is structure:
pageUser {
pageId,
userId,
type,
weight
}
user {
id,
name,
age,
city
}
This is code I have for now:
state: () => {
return {
images: [
{id: '1', name:'John', age: 23, city: 'Boston'},
{id: '2', name:'Jack', age: 34, city: 'Miami'}
]
}
},
getters: {
list: (state, pageId) => (key) => {
return map(state[key], function (s) {
return {
id: s.id,
name: s.name,
age: s.age,
city: s.city
}
})
}
This return me list of all users, but I need to make some filtering, when I go to page for example with id '2586' I need to get list of user that belong that page and they should be sorted by weight.
I'm pretty new in Vue.js and I really don't know how to make this.
getters: {
getUser: state => id => {
return user
// Create a new array of objects merging both the states
.map(u => {
return {
...pageUser.find(pu => pu.userId === u.id),
u
}
})
// Filter by id provided to the getter
.filter(u => u.id === id)
// Sort on basis of weight
.sort((a,b) => {
if (a.weight < b.weight) {
return -1
}
else if (a.weight > b.weight)) {
return 1
}
return 0
})
}
}
Call the getter in your component:
this.$store.getters.getUser('2586')

Load only the data that's needed from database with Graphql

I'm learning graphql and I think I've spot one flaw in it.
Suppose we have schema like this
type Hero {
name: String
friends: [Person]
}
type Person {
name: String
}
and two queries
{
hero {
name
friends {
name
}
}
}
and this
{
hero {
name
}
}
And a relational database that have two corresponding tables Heros and Persons.
If my understanding is right I can't resolve this queries such that for the first query the resulting sql query would be
select Heros.name, Persons.name
from Heros, Persons
where Hero.name = 'Some' and Persons.heroid = Heros.id
And for the second
select Heros.name, Persons.name from Heros
So that only the fields that are really needed for the query would be loaded from the database.
Am I right about that?
Also if graphql would have ability to return only the data that's needed for the query, not the data that's valid for full schema I think this would be possible, right?
Yes, this is definitely possible and encouraged. However, the gist of it is that GraphQL essentially has no understanding of your storage layer until you explicitly explain how to fetch data. The good news about this is that you can use graphql to optimize queries no matter where the data lives.
If you use javascript, there is a package graphql-fields that can simplify your life in terms of understanding the selection set of a query. It looks something like this.
If you had this query
query GetCityEvents {
getCity(id: "id-for-san-francisco") {
id
name
events {
edges {
node {
id
name
date
sport {
id
name
}
}
}
}
}
}
then a resolver might look like this
import graphqlFields from 'graphql-fields';
function getCityResolver(parent, args, context, info) {
const selectionSet = graphqlFields(info);
/**
selectionSet = {
id: {},
name: {},
events: {
edges: {
node: {
id: {},
name: {},
date: {},
sport: {
id: {},
name: {},
}
}
}
}
}
*/
// .. generate sql from selection set
return db.query(generatedQuery);
}
There are also higher level tools like join monster that might help with this.
Here is a blog post that covers some of these topics in more detail. https://scaphold.io/community/blog/querying-relational-data-with-graphql/
In Scala implementation(Sangria-grahlQL) you can achieve this by following:
Suppose this is the client query:
query BookQuery {
Books(id:123) {
id
title
author {
id
name
}
}
}
And this is your QueryType in Garphql Server.
val BooksDataQuery = ObjectType(
"data_query",
"Gets books data",
fields[Repository, Unit](
Field("Books", ListType(BookType), arguments = bookId :: Nil, resolve = Projector(2, (context, fields) =>{ c.ctx.getBooks(c.arg(bookId), fields).map(res => res)}))
)
)
val BookType = ObjectType( ....)
val AuthorType = ObjectType( ....)
Repository class:
def getBooks(id: String, projectionFields: Vector[ProjectedName]) {
/* Here you have the list of fields that client specified in the query.
in this cse Book's id, title and author - id, name.
The fields are nested, for example author has id and name. In this case author will have sequence of id and name. i.e. above query field will look like:
Vector(ProjectedName(id,Vector()), ProjectedName(title,Vector()),ProjectedName(author,ProjectedName(id,Vector()),ProjectedName(name,Vector())))
Now you can put your own logic to read and parse fields the collection and make it appropriate for query in database. */
}
So basically, you can intercept specified fields by client in your QueryType's field resolver.

Waterline ORM equivalent of insert on duplicate key update

I have a table user_address and it has some fields like
attributes: {
user_id: 'integer',
address: 'string' //etc.
}
currently I'm doing this to insert a new record, but if one exists for this user, update it:
UserAddress
.query(
'INSERT INTO user_address (user_id, address) VALUES (?, ?) ' +
'ON DUPLICATE KEY UPDATE address=VALUES(address);',
params,
function(err) {
//error handling logic if err exists
}
Is there any way to use the Waterline ORM instead of straight SQL queries to achieve the same thing? I don't want to do two queries because it's inefficient and hard to maintain.
The answer above is less than ideal. It also has the method as part of the attributes for the model, which is not correct behavior.
Here is what the ideal native solution looks like that returns a promise just like any other waterline model function would:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
return UserAddress.update({user_id: user_id}, {address: address});
} else {
// UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
Then you can just use it like:
UserAddress.updateOrCreate(id, address).then(function(ua) {
// ... success logic here
}).catch(function(e) {
// ... error handling here
});
Make a custom model method that does what you want using Waterline queries isntead of raw SQL. You will be doing two queries, but with Waterline syntax.
Example below (if you don't know about deferred objects then just use callback syntax, but the logic is the same):
var Q = require('q');
module.exports = {
attributes: {
user_id: 'integer',
address: 'string',
updateOrCreate: function (user_id, address) {
var deferred = Q.defer();
UserAddress.findOne().where({user_id: user_id}).then(function (ua) {
if (ua) {
// UserAddress exists. Update.
ua.address = address;
ua.save(function (err) {deferred.resolve();});
} else {
// UserAddress does not exist. Create.
UserAddress.create({user_id: user_id, address: address}).done(function (e, ua) {deferred.resolve();});
}
}).fail(function (err) {deferred.reject()});
return deferred.promise;
}
};
#Eugene's answer is good but it will always run 2 operations: findOne + update or create. I believe we can optimize it further because if the record exists we just need to run update. Example:
module.exports = {
attributes: {
user_id: 'integer',
address: 'string'
},
updateOrCreate: function (user_id, address) {
return UserAddress.update({user_id: user_id}, {address: address})
.then(function(ua){
if(ua.length === 0){
// No records updated, UserAddress does not exist. Create.
return UserAddress.create({user_id: user_id, address: address});
}
});
}
}
BTW, there is an open request to implement .updateOrCreate in waterline: #790