Polymorphs allow a single field to return different object types based on runtime conditions. This is useful when a field could contain one of several related but distinct types.
module MyAPI
module Objects
class EventSubject < Apia::Polymorph
name "Event Subject"
description "The subject of an event (could be a user, organization, or server)"
option :user, type: Objects::User, matcher: -> (value) { value.is_a?(::User) }
option :organization, type: Objects::Organization, matcher: -> (value) { value.is_a?(::Organization) }
option :server, type: Objects::Server, matcher: -> (value) { value.is_a?(::Server) }
end
end
endAdds a polymorph option. Each option defines:
name- A symbol identifying this optiontype:- The object type (or scalar/enum) to serialize the value asmatcher:- A lambda that returns true if the given value should use this option
option :user, type: Objects::User, matcher: -> (value) { value.is_a?(::User) }
option :organization, type: Objects::Organization, matcher: -> (value) { value.is_a?(::Organization) }Reference the polymorph type on a field:
class Event < Apia::Object
field :subject, Objects::EventSubject
endWhen a polymorph field is serialized, the response includes the matched type name and the serialized value:
{
"subject": {
"type": "user",
"value": {
"id": "abc-123",
"name": "John Doe",
"email": "john@example.com"
}
}
}- When serializing a polymorph field, Apia iterates through the options in order
- For each option, it calls the
matcherlambda with the actual value - The first option whose matcher returns true is used
- The value is then serialized using that option's type
- If no option matches, an
InvalidPolymorphValueErroris raised
class UserObject < Apia::Object
field :id, :string
field :name, :string
field :email, :string
end
class TeamObject < Apia::Object
field :id, :string
field :name, :string
field :member_count, :integer
endclass Assignable < Apia::Polymorph
name "Assignable"
description "Something that can be assigned to a task"
option :user, type: UserObject, matcher: -> (v) { v.is_a?(::User) }
option :team, type: TeamObject, matcher: -> (v) { v.is_a?(::Team) }
endclass Task < Apia::Object
field :id, :string
field :title, :string
field :assignee, Assignable, null: true
end