graphql-ruby
graphql-ruby copied to clipboard
GraphQL Changeset API to rename a type
Is your feature request related to a problem? Please describe.
I am trying to change the graphql_name of a type using a Changeset, but the syntax is not valid
undefined method `graphql_name' for #<GraphQL::Enterprise::Changeset::Modification:0x0000ffff76e84fb0 @schema_member=Voyager::Types::Enums::AttemptServiceName, @changeset=Changesets::VoyagerAttemptServiceRename>
Describe the solution you'd like
class Changesets::ServiceNameRename < GraphQL::Enterprise::Changeset
release "2022-09-28"
modifies Types::Enums::ServiceName do
graphql_name "BetterName"
end
end
Describe alternatives you've considered
I guess this can also be achieved by creating a new type constant which extends the original, but call graphql_name to rename the new constant (or just relies on the new constant's name reflecting what we want the graphql_name to be). This seems needlessly laborious to me though.
creating a new type constant which extends the original
Yep, that's the only way to do it currently. Under the hood, the idea that each type has exactly one name is used pretty strongly. However, it might be possible to introduce multiple-names-per-type -- I can take a look in the next couple of days and follow up here.
My thought would be to follow the same approach used for the other dynamic schema structure methods: add a context = GraphQL::Query::NullContext argument to def graphql_name, so that it can switch it return value on a per-query basis. Then update the underlying storage to support multiple names. Then update the code that builds Schema.types to check all graphql names when building that { name => [types] } map. This would also require a new method, for example, def all_graphql_names. Finally, Changeset would need a new method, def graphql_name, that registers the new name along with the changeset, and it would need an implementation of def graphql_name(context = GraphQL::Query::NullContext) that picks the right name for the incoming context.
So, it seems possible, but it'll require a bit of work 😅 !
👍 Well I've already handled this one case the type extension way. It's not too bad.
It did take me a while to wrap my head around the fact that to change a mutation's argument, I was not supposed to modify the individual mutation's class (extending GraphQL::Schema::Mutation), but instead modify the argument on the mutation field on the Mutation type 🤯. Some mutation specific pointers on https://graphql-ruby.org/changesets/definition.html wouldn't go amiss.
I ran into https://github.com/rmosolgo/graphql-ruby/issues/4564 while working on a solution to this issue. In case it inspires an upstream API to get built heres ours:
We built a mixin for all object classes that wraps the built in GraphQL Enterprise Changesets API to make it a bit less laborious to replace a type throughout the entire schema, at once. Renaming a type is done by replacing it with an identical type by a different name, so this is also useful for just renaming types.
Example: renaming an object Bovine to Cow
# Leave your existing type alone, and create a new type with the new name
class BovineType < Types::BaseObject
field :name, String, null: false
end
# Since you want the same logic, you can extend your old type for DRY-ness
class CowType < BovineType
replaces BovineType, in: Changesets::BovineToCowChangeset
end
class QueryType < Types::BaseObject
# Now switch any references to the old type to the new type.
# The framework will see that CowType.replaces is defined, and generate a backwards compatible API.
# Note: If you want to rename the field as well, you're probably better off using plain GraphQL Ent Changesets.
field :best_farm_animal, CowType, null: false
end
Under the hood we override field to generate two fields, one added_in: and one removed_in:. We also override visible? on both types to hide the appropriate type with Changeset's active? call.