graphql-ruby で Mutation を書いていて、 argument にスカラ型ではなくオブジェクトを渡す時に variables を併用する場合の書き方で詰まったのでメモを残しておく。
環境
- Rails 6.0.0
- graphql-ruby 1.9.14
目標
- createUser という mutation の argument にオブジェクトを渡す
- クライアント側のクエリでは variables を用いる
手順
MutationType にフィールドを追加する
module Types
class MutationType < Types::BaseObject
field :createUser, mutation: Mutations::CreateUserMutation
end
end
- mutation_type.rb に
createUser
というフィールドを追加し、 Mutations::CreateUserMutation を紐付ける
CreateUserMutation を実装する
module Mutations
class CreateUserMutation < GraphQL::Schema::RelayClassicMutation
graphql_name "CreateUser"
field :user, Types::UserType, null: true
field :error, String, null: true
argument :user, Types::Attributes::UserInput, required: true
def resolve(user:)
created_user = User.create(user.to_h.indifferent_access.transform_keys(&:underscore))
{ user: created_user }
rescue ActiveRecord::RecordInvalid => error
{ error: error.message }
end
end
end
-
field :user
は、この mutation 実行後のレスポンスとして取得できるフィールド。 -
Types::UserType
は普段 query で使用する BaseObject なので内容は割愛。 -
argument :user
が本題。 user というリクエストパラメータを受け取ることを宣言。そのオブジェクトの方をTypes::Attributes::UserInput
というクラスで定義している(詳細は後述)。 - 上記で宣言したリクエストパラメータを
resolve
メソッドのキーワード引数で受け取る。
ちなみに user の中身はハッシュではなく Types::Attributes::UserInput
のインスタンスになっている。
そのため中のプロパティにアクセスするには下記の二通りある。
user.postal_code # インスタンスメソッドを通してアクセス
user[:postalCode] # ハッシュのキーを通してアクセス(この時、プロパティ名はキャメルケースにする)
単純に to_h
するだけだとキーがキャメルケースのままなので、 with_indifferent_access.transform_keys(&:underscore)
という長ったらしい変換処理が必要になる。
当然、他の mutation 内でも使うことが予想されるので、 Types::BaseInputObject クラス内にインスタンスメソッドを定義してしまうと良いと思う。
module Types
class BaseInputObject < GraphQL::Schema::InputObject
argument_class Types::BaseArgument
def to_params
to_h.with_indifferent_access.transform_keys(&:underscore)
end
end
end
これなら user.to_h.indifferent_access.transform_keys(&:underscore)
を user.to_params
に置き換えられる。
Types::Attributes::UserInput を実装する
class Types::Attributes::UserInput < Types::BaseInputObject
argument :name, String, required: true
argument :gender, Integer, required: true
argument :profile, String, required: true
argument :postal_code, String, required: true
end
- 先述した resolve メソッドで受け取るキーワード引数の型を下記のように定義する。型の書き方などは query_types 等と同様。
- さらにオブジェクトをネストしたい時は Types::BaseInputObject を継承する別のクラスを指定するのかな?(試してない)
以上でサーバー側の実装は完了。
クライアント側から送信する query を書く
mutation registerUser(
$user: UserInput!
) {
createUser(input: { user: $user }) {
user { id postalCode profile }
}
}
- 1行目の
registerUser
はこのクエリに付けた適当な名前なので変えても動く。 - 2行目の
UserInput!
の!
がキモ。Mutations::CreateUserMutation
のargument
でrequired: true
を指定した場合、この!
を付けないとNullability mismatch on variable $user
というエラーが出る(ここで1時間くらいハマった)。 - 4行目の
createUser
が Types::MutationType で定義した mutation 名。 - 5行目の
user { id postalCode profile }
が mutation 実行後のレスポンスボディに入れてほしいオブジェクトとフィールドの指定。
クエリを実行
query = <<-QUERY
mutation registerUser(
$user: UserInput!
) {
createUser(input: { user: $user }) {
user { id postalCode profile }
}
}
QUERY
variables = {
karte: {
name: "midwhite",
gender: 1,
profile: "I am a software engineer."
postalCode: "000-0000"
}
}
AppSchema.execute(query, variables: variables).to_h
- variables に user オブジェクトとして渡したいパラメータをハッシュで記述する。
実際にはフロントから Apollo とか使って query や variables を送信するんだと思うけど、 query の形式さえ分かっていれば特に迷わないだろうと思うのでその部分は割愛。
ここまで分かれば mutation を実用レベルで書けそう。そろそろエンドポイントを GraphQL のみで実装したアプリケーションを書いてみたい気持ち。