Add option to require objects to define interface fields
Is your feature request related to a problem? Please describe.
The current method of interfacing seems to run counter to that of the GraphQL spec. GraphQL interfaces are not composition/mixins/etc but rather define requirements for what an object should implement. Currently this package treats interfaces like Rails concerns or mixins where the implementation and definition are all hidden in the interface.
This causes problems related to hidden code and is more of code sharing than interfacing. I don't have any problem with code sharing but that is already provided with Rails/Ruby modules and concerns. The GraphQL interfaces should simply just work like GraphQL interfaces do in the spec.
The macro implements actually is deceiving in this regard because the object doesn't actually implement the interface, the interface does it all itself, which is not really the correct way to treat interfaces.
Describe the solution you'd like
Take the current docs example of interfaces:
interface Customer {
name: String!
outstandingBalance: Int!
}
type Company implements Customer {
employees: [Individual!]!
name: String!
outstandingBalance: Int!
}
type Individual implements Customer {
company: Company
name: String!
outstandingBalance: Int!
}
I should be required to implement it the same way:
module Interfaces::Customer
include Types::BaseInterface
field :name, String, null: false
field :outstandingBalance, Int, null: false
end
class Types:: Company < Types::BaseObject
implements Interfaces::Customer
field :name, String, null: false
field :outstandingBalance, Int, null: false
field :employees,[Individual], null: false
end
class Types:: Individual < Types::BaseObject
implements Interfaces::Customer
field :name, String, null: false
field :outstandingBalance, Int, null: false
field :company, Company, null: false
end
Failing to add those attributes to the object classes would result in an implementation error.
And granted, this would be a significant change that not everyone would want to use, so having a setting to enable this would be a good idea. Something like the following maybe
GraphQL.setup |config| do
config.objects_must_implement_interfaces_fields = true
end
Describe alternatives you've considered
Currently I am just adding the fields in both places but it makes me a little nervous to hand the project off because there's no way to enforce this. I just feel like as the project goes on, more and more code is going to end up in interfaces rather than in the objects, concerns, and models like it should.
Additional context
https://graphql.org/learn/schema/#interfaces
Hi! Thanks for the suggestion and detailed writeup. Funny thing is, that's how it used to work, but enough people asked for "inheritance" that I added it. In the old schema definition API, you had to opt in with inherit: true:
https://github.com/rmosolgo/graphql-ruby/blob/8b77c12a105066a80e4827cc86cab518d55c20e3/lib/graphql/object_type.rb#L77-L81
But now "inheritance" is the default.
However, I'm game to add an option for the behavior you describe. Instead of making it a global setting, I'd like to make an interface-by-interface option, something like:
module Types::Customer
include Types::BaseInterface
# Configure this instance to hold field definitions,
# but _don't_ transfer those definitions to object types that implement this definition.
# instead, run a validation that requires objects to provide their own implementation for these fields
abstract_interface
end
By using an interface-by-interface configuration:
- Anyone who wants to start using this option can adopt it little-by-little
- Anyone who wants to use this for their whole schema can apply the setting to their
BaseInterfacemodule and it will be inherited to all interfaces
What do you think of an approach like that?
Implementation-wise, it'd need:
- A new configuration method to read/write the value
- A configuration implementation that checked for an inherited value when the module doesn't have an explicit configuration
- An update to
Schema::Object.implementsthat respects the module's configuration - A validation in
Schema::Objectto make sure all interfaces are implemented. (That validation could be run duringSchema.add_type, at which point the object type should be completely defined.)
Awesome! Haha that's rather funny. It seems to be a big topic on GraphQL's spec proposals as well. I like that approach. I think it will definitely cover the edge cases most users will come across.
Sorry, I don't have plans to work further on this, so I'm going to close the issue. If anyone wants to restart conversation on the topic, please open a new new issue. In the meantime, one option for forcing objects to implement interface fields to is to implement those fields to raise an error. That way, if they're ever called at runtime, they'll raise an error to the developer.