Open danielmahon opened 6 years ago
Here is a full gist of the integration: https://gist.github.com/2efafb579b7ab551d28e720701731510
I've tried variations on the following as well but I can't seem to get the query
and resolver
functions to run... so I must be doing something wrong.
...
// Define relationships
_.each(lists, (list, key) => {
_.each(list.relationshipFields, (field) => {
const resolverName = pluralize.isPlural(field.path) ? 'findMany' : 'findOne';
const resolver = types[field.options.ref].getResolver(resolverName);
types[key].setResolver(
// providing same name for replacing standard resolver `connection`,
// or you may set another name for keepeng standard resolver untoched
'connection',
types[key].getResolver('connection').addFilterArg({
name: field.path,
type: resolver.get('@filter'),
description: 'Filter based on slug.',
query: (rawQuery, value) => {
console.log(rawQuery, value);
// if (value.length === 1) {
// rawQuery['location.name'] = value[0];
// } else {
// rawQuery['location.name'] = { $in: value };
// }
},
}),
);
types[key].addRelation(field.path, {
resolver: () =>
resolver.wrapResolve(next => (_rp) => {
const rp = _rp;
console.log(rp);
// With object-path package set filter by one line
// objectPath.set(rp, 'args.filter._ids', rp.source.eventIds);
// or
// if (!rp.args.filter) rp.args.filter = {}; // ensure that `filter` exists
// rp.args.filter._ids = rp.source[field.path]; // set `_ids` from current User doc
// call standard `findMany` resolver with extended filter
return next(rp);
}),
prepareArgs: {
filter: (source, args) => {
console.log(source, args);
return { [pluralize.isPlural(field.path) ? '_ids' : '_id']: source[field.path] };
},
},
projection: { [field.path]: true },
});
});
});
...
... and retrieve only those images whose services contain the specified slug.
query getImagesByServiceSlug { images(filter: { services: { slug : "my-service-slug" }}) { _id name services { _id slug name } } }
Wow, you made hard work and your understanding of graphql-compose is quite strong.
How I understand you have two different collections Images
and Services
. And trying to make complex filtering on sub-requests - get all images where service has desired slug. If you have 2 collections, then you cannot make such query with graphql, cause it breaks nature of graphql:
Firstly, will be made images
request and only after its execution for every image record will be made a sub-request to services collection.
No graphql
, no graphql-compose
cannot filter data from the underlying resolver (services) for the already obtained list by the top level resolver (images).
You should reassemble your schema or write some custom resolver or filtering.
query getImagesByServiceSlug {
services(filter: { slug : "my-service-slug" }) {
_id
name
slug
images {
_id
name
}
}
}
You need to keep serviceSlugs
field for every record in the image
collection. In such case top level resolver image.findMany
will have ability to properly filter data
query getImagesByServiceSlug {
images(filter: { serviceSlugs: "my-service-slug" }) {
_id
name
serviceSlugs
}
}
You will need to use aggregations
or maybe several requests which firstly obtain required service ids by slug, and then make sub-request for obtaining images filtered by serviceId. Made it inside one resolver
.
Thanks @nodkz. I'll take a look at your solution options and let you know how I end up proceeding. I'm still getting comfortable with GraphQL but this library has saved me a TON of time, so thank you for maintaining it so well! If I can get "virtuals and/or getters" working right (right now they arent being seen by graphql-compose-mongoose
) then the 2nd solution should work well as I can just add a virtual serviceSlugs
to my Image model that updates whenever the record does.
Virtuals should be added manually, cause mongoose Models does not provide types for them. And virtual field may depend on other fields which should be requested from db in projection. So they cannot be added automatically.
Simple virtual field (assume your model has myVirtualField
getter), may be added in such way:
ImageTC.addFields({
myVirtualField: 'String', // valid types: Int, Json, Date, Float, Boolean; with wrappers [String], String!
});
If your virtual field is required data from another model's fields, then you need to add a projection:
ImageTC.addFields({
fullName: {
type: 'String',
projection: { firstName: 1, lastName: 1 },
}
});
When you request fullName
in the query then graphql-compose-mongoose
will automatically request firstName
and lastName
from database. Otherwise (without projection
) you will need to request them explicitly in your query for proper work of your virtual field.
PS. By virtual fields you cannot filter data via basic resolvers. You may do it via your own custom resolvers, but it will be a bad solution. Cause you will filter records on the backend side in the node process. Better to do filtering on database level.
OK, that makes sense. I guess I was forgetting the filtering was happening on the MongoDB level. For those watching, see https://stackoverflow.com/questions/27536383/querying-on-a-virtual-property-in-mongoose. I think I'll just add an actual serviceSlugs
field to the Image
model and filter based on that. I'd like to keep as many of the fields defined in the model itself and not GraphQL. I can still have that field automatically populated upon record create/update, I'll just have to do a minor DB migration to catch everything up. Thanks again!
@nodkz So, I've messed with adding support for running schema methods through GraphQL calls... Not sure exactly how I'm going to use them yet but might be helpful. How do I get the MongoID
type set here instead of String? (https://github.com/graphql-compose/graphql-compose-mongoose/blob/master/src/types/mongoid.js) Also, any reason why I shouldn't be attempting this?
example mutation
mutation ExampleSchemaMethod {
userGetFullName(record: {_id: "1234"}) {
payload
recordId
record {
name {
first
last
}
}
}
}
result
{
"data": {
"userGetFullName": {
"payload": "Daniel Mahon",
"recordId": "1234",
"record": {
"name": {
"first": "Daniel",
"last": "Mahon"
}
}
}
}
}
setup
...
types[`${utils.upcase(name)}SchemaMethodPayload`] = TypeComposer.create(`
type ${utils.upcase(name)}SchemaMethodPayload {
recordId: String! <!-- HOW DO I GET "MongoID!" HERE? -->
record: ${queries[name].getType()}
payload: Json
}
`);
_.each(list.model.schema.methods, (method, methodName) => {
// modelMethodName(_id): run method function by _id
mutations[name + utils.upcase(methodName)] = types[key]
.addResolver({
name: 'runSchemaMethod',
kind: 'mutation',
type: types[`${utils.upcase(name)}SchemaMethodPayload`],
args: types[key].getResolver('updateById').getArgs(),
resolve: async ({ args }) => {
// First find the record
const record = await model.findOne(args.record).exec();
// Run the methodName on the record,
// always returns the modified record and/or payload
const result = await record[methodName]();
return {
payload: result.payload,
record: result.record || record,
recordId: types[key].getRecordIdFn()(record),
};
},
})
.getResolver('runSchemaMethod');
});
...
Try to add MongoID
to schemaComposer
, then it should become available for using by name in SDL format definitions:
import { schemaComposer } from 'graphql-compose';
import { GraphQLMongoID } from 'graphql-compose-mongoose';
schemaComposer.set('MongoID', GraphQLMongoID);
...
types[`${utils.upcase(name)}SchemaMethodPayload`] = TypeComposer.create(`
type ${utils.upcase(name)}SchemaMethodPayload {
recordId: MongoID!
record: ${queries[name].getTypeName()}
payload: Json
}
`);
....
OK. Ive been trying for hours now to get the right combination of
addRelation
,addFields
andaddFilterArg
functions and I cannot seem to figure out how to allow for recursive(deep) filtering that will carry through to a populated field. I've had a hard time making sense of the previous github issues and notes/code that refer to similar methods.Id basically like to pass a query like the following and retrieve only those images whose
services
contain the specifiedslug
. I can make it work fine if I pass the service's_id
but Id like to acheive the same results by matching theslug
or any other attribute.client query
Working findMany and findOne relationships (this also works with findByIds/findById) This is a snippet of how I am mapping my mongoose models (via KeystoneJS) using
graphl-compose-mongoose
. This gets run for each model (list).I've removed all my "attempts" to make it work because it was a mess so if you can help me from this point that would be great! Thank you!