It seems that custom directives are _almost_ possible with graphql-ruby, but they are missing something akin to resolve. Ideally the resolve for a directive would be able to either modify the output of the original resolve, or prevent it from being executed.
Something like this would be nice.
GraphQL::Directive.define do
name 'bananaMe'
description 'Does what it needs to.'
locations [GraphQL::Directive::FIELD, GraphQL::Directive::FRAGMENT_SPREAD, GraphQL::Directive::INLINE_FRAGMENT]
argument :force, !types.Boolean, 'Forces banana.'
resolve ->(original_resolve, obj, args, ctx) {
if args[:force]
'Banana'
else
original_resolve(obj, args, ctx)
end
}
end
Right now that logic seems to exist here. It would take a real hack to change it without support.
almost possible
I agree! I was looking at options while rewriting the query transformation last week but alas, left it as a # TODO.
I'm also interested in this because I want to implement @defer and @stream (and maybe @live!) in a non-intrusive, non-rewrite-everything way.
Here's kind of what I was imagining:
Add a hook to schema definitions where people can opt into directives:
MySchema = GraphQL::Schema.define do
# map `@banana` to user-defined `BananaDirective`
directive banana: BananaDirective
end
Add an API for directives to modify flagged nodes (like resolve in your example above). I'm not _sure_ if wrapping resolve is enough for defer and stream because, when nodes are deferred, they should _entirely_ absent from the response, not nil. So I may need to add a way to modify the query itself.
Port the current @skip and @include to this pattern, and add some kind of global GraphQL::Schema.default_directives map which includes them and applies them by default.
What do you think of something like that?
That sounds great!
I don't think having the modification API be a little complex is a huge deal either, as long as the logic can be easily contained. Writing a directive is a pretty rare activity.
It hit me last night while I was falling asleep, and now I confirmed, the current directive approach is flawed:

馃槚 (id should not be present in that response since its parent was skipped)
They're processed _after_ rewriting the query. I guess we need to skip nodes at the AST level so they never enter the rewritten tree.
I'll keep you posted on this issue but I'll need some time before I can get a powerful & maintainable public API for this!
Starting to take a look here: https://github.com/rmosolgo/graphql-ruby/pull/554
It's going to be harder than that: for @defer, for example, we need:
@defer, maintaining everything downstream from a @defer in a separate tree I don't want to commit to an API until I can be sure that bases are covered for known use cases. Did you have any specific ideas in mind? If you're interested in sharing, I'd be happy to consider them when I get more time to work on that.
I don't have any specific solutions to those problems, unfortuately. I've browsed the source but don't have a deep understanding of how everything works at this point.
The use-case I had in mind was to use a custom directive for a feature-flag system. Something like:
query {
subtree @ifFeature(flag: "feature-flag") {
...
}
}
Oh right, I meant _directive_ ideas, not rewriting-the-internals-ideas, so I think I'm with you there 馃槅
That's interesting -- it's like skip, but instead of getting the if: value from the query, it makes a test based on the current user?
Pretty much. We have a single codebase that runs a bunch of sites with mostly-but-not-completely overlapping feature sets. We manage that complexity with a feature flag system that also handles multivariate testing and staged rollouts.
Did you find a good way to handle this in the meantime?
Not really. Right now I'm waiting for the first graph query to return with the feature flag data, then re-fetching the branches I don't have. The directive could simplify this into one query.
Is nullability an option? I mean, could those fields return nil in cases when the current user isn't flagged for them?
(Or, maybe the relation between fields and flags isn't one-to-one?)
nil would be fine, but ya, which fields need to behind the feature flags is context-dependent.
Heya, i really want custom directives to work in a sense of meta data. Is there any way i can achieve that now? Basically just to annotate my models for my generators like how they do here:
https://www.graph.cool/graphql-up/
type Tweet {
id: ID!
title: String!
author: User! @relation(name: "Tweets")
}
type User {
id: ID!
name: String!
tweets: [Tweet!]! @relation(name: "Tweets")
}
There's no way you can achieve it now. It's something I'm interested in too though! If you want to hack on it, here's were schema definitions are turned into Ruby objects:
https://github.com/rmosolgo/graphql-ruby/blob/master/lib/graphql/schema/build_from_definition.rb#L54-L74
It looks like field definitions already support directives, so you can get the directive AST nodes with .directives.
@rmosolgo Just curious, how can I re use a result of a query to make another query within graphql e.g
mutation M {
createUser(firstName: 'bob') { id }
createPreferences(user_id: #createUser.id, preferences: "doesn't appreciate purple") {
success
}
}
how can I reuse the id from createUser in createPreferences
Runtime directives are available in 1.9.x: https://graphql-ruby.org/type_definitions/directives.html#custom-directives
Most helpful comment
It hit me last night while I was falling asleep, and now I confirmed, the current directive approach is flawed:
馃槚 (
idshould not be present in that response since its parent was skipped)They're processed _after_ rewriting the query. I guess we need to skip nodes at the AST level so they never enter the rewritten tree.
I'll keep you posted on this issue but I'll need some time before I can get a powerful & maintainable public API for this!