LoginSignup
5
1

More than 3 years have passed since last update.

graphql-rubyでpolymorphicを表現する

Last updated at Posted at 2020-05-31

はじめに

最近、graphql-rubyでAPIを実装しています。
Railsでは、モデルの関連付けにポリモーフィックという便利な機能があります。
これをUnionTypeを使ってgraphqlでも表現する方法について説明します。

やりたいこと

UserテーブルとPetShopテーブルがDogテーブルのownerableでポリモーフィックな場合を考えます。graphqlでdogを取得するときに、その所有者であるownerable(user or pet_shop)のカラムも一緒にとってこれるようにする。

ポリモーフィックではない場合

まず復習をかねて、通常の関連付け(belongs_toとかhas_manyとか)の場合を考えてみます。
以下のようにUserとDogが1対多だとします。

user.rb
Class User < ApplicationRecord
  has_many: dogs
end
dog.rb
Class Dog < ApplicationRecord
  belongs_to: user
end

ObjectType

モデルで定義した関連付けをgraphql-rubyで表現するには、ObjectTypeを以下のように書きます。

user_type.rb
Class UserType < Types::BaseObject
  field :id, ID, null: false
end
dog_type.rb
Class DogType < Type::BaseObject
  field :name, String, null: false
  field :user, ObjectTypes::UserType, null: false # userとの関連付け
end

上記のようにObjectTypeを定義すると、dogオブジェクトを返す際に一緒に関連付けられたuserオブジェクトも取得してくれます。

ポリモーフィックの場合

続いて、ポリモーフィックで関連づけられた場合を考えてみます。
userの他にpetShopもdogを所有しています。

モデルの関連付け

ポリモーフィックの場合、Rialsモデルの関連付けは以下のようにかけます。

user.rb
Class User < ApplicationRecord
  has_many :dogs, as: :ownerable
end
pet_shop.rb
Class PetShop < ApplicationRecord
  has_many :dogs, as: :ownerable
end
dog.rb
Class Dog < ApplicationRecord
  belongs_to :ownerable, polymorphic: true
end

ObjectType

続いて、Objectタイプを定義します。
petShopオブジェクトが追加されたのと、dogTypeのfieldがuserではなく、ポリモーフィックな関連付けを示すownerableに変更されています。

user_type.rb
Class UserType < Types::BaseObject
  field :id, ID, null: false
end
pet_shop_type.rb
Class PetShopType < Types::BaseObject
  field :id, ID, null: false
end
dog_type.rb
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かを振り分けてくれます。

ownerable_type.rb
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を選択することができます。

5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1