Polymorphic types with GraphQL-ruby
Polymorphic associations in Rails allow a model to belong to multiple other models through a single association. In GraphQL-ruby, you can represent polymorphic types using Union types, which allow a field to return one of several possible types.
What are Polymorphic Types?
Polymorphic associations in Rails look like this:
class Comment < ApplicationRecord
belongs_to :commentable, polymorphic: true
end
class Post < ApplicationRecord
has_many :comments, as: :commentable
end
class Photo < ApplicationRecord
has_many :comments, as: :commentable
end
In GraphQL, we represent this using Union types.
Creating Union Types
Define the Union Type
Create a union type that represents all possible types:
# app/graphql/types/commentable_type.rb
module Types
class CommentableType < Types::BaseUnion
possible_types Types::PostType, Types::PhotoType
def self.resolve_type(object, context)
case object
when Post
Types::PostType
when Photo
Types::PhotoType
else
raise "Unknown commentable type: #{object.class}"
end
end
end
end
Define Individual Types
Define the types that can be part of the union:
# app/graphql/types/post_type.rb
module Types
class PostType < Types::BaseObject
field :id, ID, null: false
field :title, String, null: false
field :content, String, null: false
end
end
# app/graphql/types/photo_type.rb
module Types
class PhotoType < Types::BaseObject
field :id, ID, null: false
field :url, String, null: false
field :caption, String, null: true
end
end
Using Union Types
In Comment Type
Use the union type in your Comment type:
# app/graphql/types/comment_type.rb
module Types
class CommentType < Types::BaseObject
field :id, ID, null: false
field :content, String, null: false
field :commentable, Types::CommentableType, null: false
end
end
Query Example
Query polymorphic types:
query GetComments {
comments {
id
content
commentable {
... on Post {
id
title
content
}
... on Photo {
id
url
caption
}
}
}
}
Resolving Polymorphic Types
Automatic Resolution
GraphQL-ruby can automatically resolve types based on the object class:
module Types
class CommentableType < Types::BaseUnion
possible_types Types::PostType, Types::PhotoType
def self.resolve_type(object, context)
# GraphQL-ruby will try to match object.class.name to Type name
# Post -> PostType, Photo -> PhotoType
Types.const_get("#{object.class.name}Type")
rescue NameError
raise "Unknown type: #{object.class.name}"
end
end
end
Manual Resolution
For more control, manually resolve types:
def self.resolve_type(object, context)
case object
when Post
Types::PostType
when Photo
Types::PhotoType
when Video
Types::VideoType
else
raise "Unknown commentable type: #{object.class}"
end
end
Real-World Example
Activity Feed
Create an activity feed with different activity types:
# app/models/activity.rb
class Activity < ApplicationRecord
belongs_to :subject, polymorphic: true
end
# app/graphql/types/activity_type.rb
module Types
class ActivityType < Types::BaseObject
field :id, ID, null: false
field :created_at, GraphQL::Types::ISO8601DateTime, null: false
field :subject, Types::ActivitySubjectType, null: false
end
end
# app/graphql/types/activity_subject_type.rb
module Types
class ActivitySubjectType < Types::BaseUnion
possible_types Types::PostType, Types::CommentType, Types::LikeType
def self.resolve_type(object, context)
case object
when Post
Types::PostType
when Comment
Types::CommentType
when Like
Types::LikeType
else
raise "Unknown activity subject: #{object.class}"
end
end
end
end
Query Activity Feed
query GetActivityFeed {
activities {
id
createdAt
subject {
... on Post {
id
title
}
... on Comment {
id
content
}
... on Like {
id
user {
name
}
}
}
}
}
Common Fields
If all types in the union share common fields, you can query them directly:
# If both Post and Photo have an 'id' field
query GetComments {
comments {
id
content
commentable {
id # Works if both types have id
... on Post {
title
}
... on Photo {
url
}
}
}
}
Best Practices
- Use descriptive union names: Name unions based on their purpose
- Handle all cases: Ensure
resolve_typehandles all possible types - Provide fallback: Raise clear errors for unknown types
- Document types: Add descriptions to union and type definitions
Advanced: Interface Types
For shared fields, consider using interfaces:
# app/graphql/types/commentable_interface.rb
module Types
module CommentableInterface
include Types::BaseInterface
field :id, ID, null: false
field :comments, [Types::CommentType], null: false
def comments
object.comments
end
end
end
# Implement in types
module Types
class PostType < Types::BaseObject
implements Types::CommentableInterface
field :title, String, null: false
end
end
Error Handling
Handle unknown types gracefully:
def self.resolve_type(object, context)
case object
when Post
Types::PostType
when Photo
Types::PhotoType
else
GraphQL::ExecutionError.new(
"Unknown commentable type: #{object.class.name}",
extensions: { code: "UNKNOWN_TYPE" }
)
end
end
Conclusion
Polymorphic types in GraphQL-ruby are implemented using Union types, which allow a field to return one of several possible types. By using Union types with proper type resolution, you can represent Rails polymorphic associations in your GraphQL API, providing flexible and type-safe queries for your clients.