graphql-compose-mongoose
graphql-compose-mongoose copied to clipboard
update Mutation: push to Array instead of replacing it.
Let's say I have a Schema like this:
const mySchema = new Schema({
foo: [{ type: String }]
}]
And I want to update an existing Document - from what I know currently we only have the option to replace our fields with new ones. Are there any options to
- Push certain elements to a field
- Pop certain elements from a field
Example push: I have a document
{
foo: [ "a", "b", "c"]
}
and want to add "d"
to the array.
Currently I would need to
- Query the document to get foo
- Create a new array
- Mutate the document with the new array
which would cost many recources
or
- Create a custom Mutation that pushes instead of updates
which I see problematic if we have Schemas with multiple arrays and we want to allow mutations like
- update Array1
- replace Array2
Same Problems exist with popping elements.
I think it would be great if we had some kind of options in the Mutation itself. It would be super neat if we had a syntax that allows stuff like this:
{
_id: "6099cb18f294f83dbcbf8936",
foo: ["blue"],
bar: ["dolores"],
xyz: ["deleteThis", "butNotThat"],
}
mutation {
somethingUpdateById(
_id: "6099cb18f294f83dbcbf8936"
record: {
foo: ["green", "yellow"]
bar: ["Lorem", "Ipsum"]
xyz: ["deleteThis"]
}
options: {
arrays: {
foo: replace
bar: push
xyz: pop
}
}
}) {
record {
foo
bar
xyz
}
}
}
#response
{
foo: ["green", "yellow"]
bar: ["dolores", "Lorem", "Ipsum"]
xyz: ["butNotThat"]
}
I know this is asking for extremely much, but perhaps I inspire someone to implement something like it. My skills unfortunately aren't high enough to do it myself.
Interesting idea 👍
But the current suggested implementation looks now like a hack. And it can bring a lot of problems in the future when we want to migrate on InputUnions or refactor the suggested solution without breaking changes. Now it just brings complexity to the current resolver implementation. But right now record
& options
will not cover all user cases – what if we need to unset some fields?! So I think that we need provide some solution that will cover 95% of different uses cases with mongodb operators (push, pop, set, unset, ...) https://docs.mongodb.com/manual/reference/operator/update/
I think we need to await @oneOf
implementation https://github.com/graphql/graphql-spec/pull/825 which unlocks InputUnions, and we can use it for the current or similar issues.
ANYWAY I'm glad to see any suggestion because BEST PRACTICES arise only in the "battle" (practice, implementation, design, discussions). But we should avoid any modifications until we do not become sure that it's a good solution for most developers. Better not to do something rather than provide a temporary thing.
@riggedCoinflip, as a workaround I suggest you write a custom resolver with any desirable logic.
I went for the custom resolver.
This snippet allows to push and pop MongoIDs from a block list and allows certain other fields to be updated.
schemaComposer.createInputTC({
name: "UserPrivateBlockedMutation",
fields: {
toPush: ["MongoID"],
toPop: ["MongoID"],
}
})
UserTCPrivate.addResolver({
kind: "mutation",
name: "userUpdateSelf",
description: "Update currently logged in user",
args: {
name: "String",
gender: "EnumUserPrivateGender",
blocked: "UserPrivateBlockedMutation",
},
type: UserTCPrivate,
resolve: async ({args, context}) => {
/**
* @param {Array} arr - array to filter
* @param {Array} values - values to filter out
* @returns {Array} filtered
*/
function filterByValues(arr, values) {
return arr.filter(
itemArray => { !values.some(itemValues => itemArray.equals(itemValues)})
}
const userSelf = await User.findOne({_id: context.req.user._id})
if (args.name) userSelf.name = args.name
if (args.gender) userSelf.gender = args.gender
if (args.blocked?.toPush) userSelf.blocked.push(...args.blocked.toPush)
if (args.blocked?.toPop) userSelf.blocked = filterByValues(userSelf.blocked, args.blocked.toPop)
await userSelf.save()
return User.findOne({_id: context.req.user._id})
}
})
Query:
#Template Query
mutation userUpdateSelf(
$name: String
$gender: EnumUserPrivateGender
$blocked: UserPrivateBlockedMutation
) {
userUpdateSelf(
name: $name
gender: $gender
blocked: $blocked
) {
name
gender
blocked
}
}
#Example
mutation {
userUpdateSelf(
name: "MyNewName"
gender: female
blocked: {
toPush: ["MongoIDsOfUsersYouWishToBlock"]
toPop: ["MongoIDsOfUsersInYourBlocklist"]
}
) {
name
gender
blocked
}
}