はじめに
最近、graphql-rubyでAPIを実装しています。
Railsでは、モデルの関連付けにポリモーフィックという便利な機能があります。
これをUnionTypeを使ってgraphqlでも表現する方法について説明します。
やりたいこと
UserテーブルとPetShopテーブルがDogテーブルのownerableでポリモーフィックな場合を考えます。graphqlでdogを取得するときに、その所有者であるownerable(user or pet_shop)のカラムも一緒にとってこれるようにする。
ポリモーフィックではない場合
まず復習をかねて、通常の関連付け(belongs_toとかhas_manyとか)の場合を考えてみます。
以下のようにUserとDogが1対多だとします。
Class User < ApplicationRecord
has_many: dogs
end
Class Dog < ApplicationRecord
belongs_to: user
end
ObjectType
モデルで定義した関連付けをgraphql-rubyで表現するには、ObjectTypeを以下のように書きます。
Class UserType < Types::BaseObject
field :id, ID, null: false
end
Class DogType < Type::BaseObject
field :name, String, null: false
field :user, ObjectTypes::UserType, null: false # userとの関連付け
end
上記のようにObjectTypeを定義すると、dogオブジェクトを返す際に一緒に関連付けられたuserオブジェクトも取得してくれます。
ポリモーフィックの場合
続いて、ポリモーフィックで関連づけられた場合を考えてみます。
userの他にpetShopもdogを所有しています。
モデルの関連付け
ポリモーフィックの場合、Rialsモデルの関連付けは以下のようにかけます。
Class User < ApplicationRecord
has_many :dogs, as: :ownerable
end
Class PetShop < ApplicationRecord
has_many :dogs, as: :ownerable
end
Class Dog < ApplicationRecord
belongs_to :ownerable, polymorphic: true
end
ObjectType
続いて、Objectタイプを定義します。
petShopオブジェクトが追加されたのと、dogTypeのfieldがuserではなく、ポリモーフィックな関連付けを示すownerableに変更されています。
Class UserType < Types::BaseObject
field :id, ID, null: false
end
Class PetShopType < Types::BaseObject
field :id, ID, null: false
end
Class DogType < Type::BaseObject
field :name, String, null: false
field :ownerable, UnionTypes::OwnerableType, null: false # ownerableとの関連付け
end
注意点はownerableのtypeがObjectTypesではなく、UnioTypesになっている点です。
UnionTypeはこれから定義していきます。
UnioneType
UnioTypeは、本記事の肝となる箇所です。dogTypeに定義したownerableをUnionTypeとして定義して、userTypeかpetShopTypeかを振り分けてくれます。
module UnionTypes
class OwnerableType < Types::BaseUnion
# possible_typesで関連付けられうるタイプを宣言する
possible_types ObjectTypes::UserType, ObjectTypes::PetShopType
# 分岐処理を記述
def self.resolve_type(object, _context)
if object.is_a?(User)
ObjectTypes::PostType
elsif object.is_a?(PetShop)
ObjectTypes::ReplyType
end
end
end
end
上記のように書くことで、resolverで返す型を判断して、UserかPetShopを選んでくれます。(rubyっぽくはない感じはしますが...)
Query
UnionTypeを使った場合は、queryも少し特殊な書き方をします。
query{
dog(id: [dog_id]){
id
ownerable {
__typename #型を指定する
... on User {
id
}
__typename
... on PetShop {
id
}
}
}
}
上記のようにtypenameを指定することによってほしいownerを選択することができます。