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

  1. Use descriptive union names: Name unions based on their purpose
  2. Handle all cases: Ensure resolve_type handles all possible types
  3. Provide fallback: Raise clear errors for unknown types
  4. 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.

Polymorphic types with GraphQL-ruby - Abhay Nikam