GraphQL で動的なクエリを構築したい時、 Directive が便利です。
graphql-ruby の場合
組み込みディレクティブ
- @skip(if: ...) skips the selection if the if: ... value is truthy (GraphQL::Schema::Directive::Skip)
- @include(if: ...) includes the selection if the if: ... value is truthy (GraphQL::Schema::Directive::Include)
例
$renderingDetailProfile: true
のときだけ以下のフィールドが含まれる
query ProfileView($renderingDetailedProfile: Boolean!){
viewer {
handle
# These fields will be included only if the check passes:
... @include(if: $renderingDetailedProfile) {
location
homepageUrl
}
}
}
カスタムディレクティブを定義できる
- カスタムディレクティブを定義する
# app/graphql/directives/my_directive.rb
class Directives::MyDirective < GraphQL::Schema::Directive
description "A nice runtime customization"
# フィールド名を指定する
location FIELD
end
- ディレクティブをスキーマに反映させる
class MySchema < GraphQL::Schema
# Attach the custom directive to the schema
directive(Directives::MyDirective)
end
- クエリの中で呼び出すことができる
query {
field @myDirective {
id
}
}
具体例
カスタムディレクティブを利用してクライアントに応じた動的なクエリを設計する方法
要件
- RedというアプリとBlueというアプリがある
- 各アプリから同じクエリを呼び出すが、アプリに応じて変えたいフィールドがある
実現方法
- まず、GraphQL::Schema::Directiveを継承したクラスを作成します。このクラスには、ディレクティブの名前、説明、引数、適用できる場所などを定義します。例えば、@clientというディレクティブを作る場合は、以下のようになります。
# app/graphql/directives/client.rb
class Directives::Client < GraphQL::Schema::Directive
description "Filters the field value by the client name"
argument :name, String, required: true
locations FIELD_DEFINITION
end
- 次に、スキーマにディレクティブを登録します。これは、directiveメソッドを使って行います。例えば、以下のようになります。
class MySchema < GraphQL::Schema
# Register the custom directive to the schema
directive(Directives::Client)
end
- 最後に、ディレクティブを適用したいフィールドや型に@clientという記法で付与します。引数には、対象とするクライアントの名前を指定します。例えば、以下のようになります。
module Types
class BookType < Types::BaseObject
field :title, String, null: false
field :author, String, null: false
field :price, Int, null: false, directive: { client: { name: "Red" } }
field :rating, Int, null: false, directive: { client: { name: "Blue" } }
end
end
- これで、カスタムディレクティブの設定は完了です。しかし、これだけでは実際にディレクティブの効果が反映されません。ディレクティブのロジックを実装するためには、resolveメソッドをオーバーライドする必要があります。このメソッドは、フィールドの値を受け取り、ディレクティブの引数やコンテキストに応じて変換して返します。例えば、@clientディレクティブの場合は、以下のようになります。
# app/graphql/directives/client.rb
class Directives::Client < GraphQL::Schema::Directive
# ...
def self.resolve(obj, args, ctx)
# Get the original field value
value = yield
# Get the client name argument
client_name = args[:name]
# Get the current client name from the context
current_client = ctx[:client]
# Return the value only if the client names match, otherwise return nil
if client_name == current_client
value
else
nil
end
end
end
- これで、カスタムディレクティブの実装は完了です。クエリを実行すると、ディレクティブが適用されたフィールドの値がフィルタされて返されます。例えば、以下のような結果が得られます。
# Red app query:
{
"data": {
"book": {
"title": "The Little Prince",
"author": "Antoine de Saint-Exupéry",
"price": 1000,
"rating": null // This field is filtered by @client(name: "Blue")
}
}
}
# Blue app query:
{
"data": {
"book": {
"title": "The Little Prince",
"author": "Antoine de Saint-Exupéry",
"price": null, // This field is filtered by @client(name: "Red")
"rating": 5
}
}
}
以上が、graphql-rubyでカスタムディレクティブを利用して、クライアント側のアプリに応じたクエリの設計をする方法です。 詳しくは公式ドキュメントを参照してください。