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でカスタムディレクティブを利用して、クライアント側のアプリに応じたクエリの設計をする方法です。 詳しくは公式ドキュメントを参照してください。