YASD-TECH
YASD TECH
# GraphQL

field DSL の引数と使い分け(graphql-ruby)

投稿日:2026/8/5

更新日:2026/8/5

ttitleImage

field DSL の引数と使い分け(graphql-ruby)

field は「GraphQL のスキーマに 1 行足す」DSL。シグネチャは以下。

ruby
field(name, type, description = nil, **options, &block)

実体は GraphQL::Schema::Field.new で、位置引数はすべてキーワードでも書ける(name: / type: / description:)。
以下は graphql 2.6.5 のソースを読んで確認した内容。

結論

  • 位置引数は 3 つだけ。nametypedescription。残りは全部キーワード
  • null: を省略すると true(nullable)。non-null にしたいときだけ null: false を書く
  • リストの内側は逆にデフォルト non-null[String][String!] になる
  • 値の解決先は「型クラスのメソッド → モデルのメソッド」の順。ここを差し替えるのが method: / hash_key: / resolver_method:

最小の例

ruby
module Types
  class OrderType < Types::BaseObject
    field :id, ID, null: false
    field :total_price, Integer, null: false, description: '税込の合計金額(円)'
    field :note, String                                   # null: 省略 = nullable
    field :items, [Types::OrderItemType], null: false     # [OrderItem!]!
  end
end

生成される SDL。

graphql
type Order {
  id: ID!
  """税込の合計金額(円)"""
  totalPrice: Int!
  note: String
  items: [OrderItem!]!
}

Ruby 側は snake_case、スキーマ側は camelCase。この変換は camelize:(デフォルト true)が担当している。


第 1 引数:name

  • Symbol でも String でもよい。慣例は Symbol
  • camelize: true(デフォルト)で total_pricetotalPrice に変換される
  • 名前は同時に「呼び出すメソッド名」のデフォルト値にもなる(後述の解決順を参照)
ruby
field :total_price, Integer, null: false                  # totalPrice
field :total_price, Integer, null: false, camelize: false # total_price(スキーマ側もそのまま)

第 2 引数:type

型の書き方は 4 通りある。

書き方 備考
Ruby の組み込みクラス String / Integer / Float 内部で GraphQL::Types::String などに変換される
graphql-ruby の定数 ID / Int / Boolean 実体は "ID" などの文字列定数(GraphQLTypeNames
型クラス Types::OrderType 自作の型
文字列 "Types::OrderType" 遅延解決。循環参照や読み込み順の問題を避けたいとき

リストは配列で囲む。

ruby
field :tags, [String], null: false          # [String!]!   ← 内側は non-null がデフォルト
field :tags, [String, null: true], null: false  # [String]!  ← 内側も nullable にする

[T] の内側がデフォルトで non-null なのは、フィールド本体の null: デフォルト(true)と逆向き
ここは毎回間違えるので、[String, null: true] という特殊な書き方があることだけ覚えておく。

null: は型ではなくキーワードで表す

String! のような ! は書けない(field :name, String! は Ruby の構文として通らない)。non-null は null: false で表現する。

ruby
field :name, String, null: false   # name: String!
field :name, String                # name: String

省略時のデフォルトは null: true。ソース上も nullnil のときは true にフォールバックしている。
ただし resolver: を使う場合だけは、省略するとリゾルバ側の null 設定を引き継ぐ


第 3 引数:description

位置引数でも description: キーワードでも書ける。両方書くと ArgumentError

ruby
field :total_price, Integer, '税込の合計金額(円)', null: false   # 位置引数
field :total_price, Integer, null: false, description: '税込の合計金額(円)'  # キーワード

ブロック内で description を呼ぶ形もある。

ruby
field :total_price, Integer, null: false do
  description '税込の合計金額(円)'
end

値がどう解決されるか

field を書いただけで値が返るのは、graphql-ruby が決まった順番でメソッドを探しているから。

flowchart TD
    A["field :total_price"] --> B{"resolver: / mutation: がある?"}
    B -->|Yes| B1["そのクラスの #resolve を呼ぶ"]
    B -->|No| C{"hash_key: がある?"}
    C -->|Yes| C1["object[hash_key]"]
    C -->|No| D{"型クラスに<br/>#total_price がある?"}
    D -->|Yes| D1["Types::OrderType#total_price"]
    D -->|No| E{"object が Hash?"}
    E -->|Yes| E1["object[:total_price] / object['total_price']"]
    E -->|No| F{"モデルに<br/>#total_price がある?"}
    F -->|Yes| F1["Order#total_price"]
    F -->|No| G{"fallback_value: がある?"}
    G -->|Yes| G1["その値"]
    G -->|No| H["例外:Failed to implement ..."]

つまり普段は「④ モデルのメソッドがそのまま呼ばれている」だけ。型クラスに同名メソッドを定義すると、そちらが優先される。

ruby
module Types
  class OrderType < Types::BaseObject
    field :total_price, Integer, null: false

    # ここに書くとモデルより優先される(object = Order インスタンス)
    def total_price
      object.price + object.tax
    end
  end
end

どこにも見つからないときのエラーは、探した場所をそのまま出してくれる。

Failed to implement Order.totalPrice, tried:

- `Types::OrderType#total_price`, which did not exist
- `Order#total_price`, which did not exist
- Looking up hash key `:total_price` or `"total_price"` on `#<Order id: 1>`, but it wasn't a Hash

To implement this field, define one of the methods above (and check for typos), or supply a `fallback_value`.

解決先を差し替えるオプション

オプション 呼ぶ先 使う場面
method: モデルの別メソッド スキーマ名とモデルのメソッド名を変えたい
hash_key: object[key] object が Hash(集計結果や外部 API のレスポンス)
dig: object.dig(*keys) ネストした Hash から取り出す(object が Hash のときのみ)
resolver_method: 型クラスの別メソッド 型クラス側のメソッド名を変えたい(名前衝突の回避)
fallback_value: 固定値 メソッドが無くても落としたくない
ruby
field :amount, Integer, null: false, method: :total_price          # Order#total_price
field :count, Integer, null: false, hash_key: :cnt                 # object[:cnt]
field :city, String, dig: [:address, :city]                        # object.dig(:address, :city)
field :object, Types::PayloadType, resolver_method: :resolve_object # 型クラスの #resolve_object
field :legacy_flag, Boolean, null: false, fallback_value: false

method: / hash_key: / dig: / resolver_method:組み合わせによっては ArgumentError
method:resolver_method: の併用、hash_key:(または dig:)と resolver_method: の併用は不可。


引数(argument)を取るフィールド

field にブロックを渡して argument を並べる。

ruby
field :orders, [Types::OrderType], null: false do
  argument :status, Types::Enum::OrderStatusEnum, required: false
  argument :limit, Integer, required: false, default_value: 20
end

def orders(status: nil, limit: 20)
  scope = object.orders
  scope = scope.where(status:) if status
  scope.limit(limit)
end

GraphQL の引数は、解決メソッドにキーワード引数として渡ってくる。受け口のキーワードが足りないと実行時に落ちるので、required: false の引数はデフォルト値付きで受ける。

argument の主なオプション。

オプション 意味
required: true(デフォルト= non-null) / false / :nullable(必須だが null を許す)
default_value: 省略時の値
as: Ruby 側で受け取るキーワード名を変える
prepare: 受け取った値を変換してから渡す
loads: ID を受け取って対象オブジェクトをロードする(xxx_idxxx
camelize: 引数名の camelCase 化(デフォルト true
deprecation_reason: 非推奨マーク(required: true とは併用不可)

よく使うその他のオプション

オプション 用途
deprecation_reason: 非推奨マークを付ける。フィールドは残したまま IDE / Playground に警告が出る
resolver: GraphQL::Schema::Resolver のサブクラスに解決を委譲する
mutation: GraphQL::Schema::Mutation のサブクラスを紐付ける(Mutation 型で使う)
subscription: Subscription クラスを紐付ける
connection: Relay コネクションとして扱うか。デフォルトは型名が Connection で終わるかで自動判定
max_page_size: / default_page_size: コネクションの取得件数の上限・デフォルト
scope: 返り値に型の .scope_items を適用するか(リスト/コネクションは自動で true
extras: :lookahead :ast_node :parent :path :execution_errors などを解決メソッドに追加で渡す
complexity: クエリの複雑度。数値または ->(ctx, args, child_complexity) { ... }
extensions: FieldExtension を適用する(共通の前後処理)
introspection: __ 始まりの名前を許可する内部フィールド
ruby
# 非推奨化
field :name, String, null: false, deprecation_reason: 'full_name を使ってください'

# リゾルバに委譲
field :orders, resolver: Resolvers::OrdersSearch

# lookahead で N+1 を先読み判定
field :orders, [Types::OrderType], null: false, extras: [:lookahead]

def orders(lookahead:)
  scope = object.orders
  scope = scope.preload(:items) if lookahead.selects?(:items)
  scope
end

# 複雑度
field :report, Types::ReportType, complexity: 30

BaseField に共通設定を寄せる

field が使うクラスは型クラスの field_class で決まる。全フィールド共通の振る舞い(認可、ログ、独自オプション)はここに置く。

ruby
module Types
  class BaseField < GraphQL::Schema::Field
    argument_class Types::BaseArgument

    def initialize(*args, permission: nil, **kwargs, &block)
      @permission = permission
      super(*args, **kwargs, &block)
    end

    def visible?(context)
      return false if @permission && !context[:current_user]&.can?(@permission)
      super
    end
  end
end

module Types
  class BaseObject < GraphQL::Schema::Object
    field_class Types::BaseField
  end
end
ruby
field :internal_memo, String, permission: :admin

field は知らないキーワードを渡すと ArgumentError になるので、独自オプションは必ず BaseField#initialize で受け取ってから super に渡す。


ハマりどころ

null: を書き忘れると nullable になる

昔のバージョンは null: が必須だったが、現在は省略可= true 扱い。「書き忘れ」が黙って nullable なスキーマになる。
non-null を基本にしたいなら BaseField でデフォルトを反転させるか、rubocop / スキーマの差分レビューで気づける状態にしておく。

② リストの内側の nullability

ruby
field :tags, [String], null: false   # [String!]!

「配列自体も要素も non-null」になる。要素に nil が混ざると実行時エラーになるので、compact 漏れがあると本番で落ちる。
要素に nil があり得るなら [String, null: true]

③ 組み込みメソッドと名前が衝突すると警告が出る

field :methodfield :object のように、Object のインスタンスメソッド・Ruby の予約語・graphql-ruby の予約語(context / object / raw_value)と同名だと、定義時に警告が出る。

ruby
field :object, Types::OrderType, null: false, resolver_method: :resolve_object

def resolve_object
  object.target
end

resolver_method: を指定すると警告も消える。どうしても黙らせたいだけなら method_conflict_warning: false

④ 型の循環参照は文字列で回避する

OrderTypeUserType を、UserTypeOrderType を参照するようなケースでは、定数解決の順番で uninitialized constant になることがある。型名を文字列で書くと遅延解決になる。

ruby
field :user, "Types::UserType", null: false

定数の探索順そのものは 定数の参照とダブルコロン(スコープ解決演算子) を参照。

description の二重指定

位置引数とキーワードの両方を書くと ArgumentError。リファクタで片方を消し忘れると定義時に落ちる(実行時ではないので気づきやすい)。

⑥ コネクション判定は型名依存

connection: を明示しない場合、返り値の型名が Connection で終わるかだけで判定される。独自のページネーション型に Connection という名前を付けると、意図せず Relay の引数(first / after など)が生える。


参考

関連

Index

  • field DSL の引数と使い分け(graphql-ruby)
  • 結論
  • 最小の例
  • 第 1 引数:name
  • 第 2 引数:type
  • null: は型ではなくキーワードで表す
  • 第 3 引数:description
  • 値がどう解決されるか
  • 解決先を差し替えるオプション
  • 引数(argument)を取るフィールド
  • よく使うその他のオプション
  • BaseField に共通設定を寄せる
  • ハマりどころ
  • ① null: を書き忘れると nullable になる
  • ② リストの内側の nullability
  • ③ 組み込みメソッドと名前が衝突すると警告が出る
  • ④ 型の循環参照は文字列で回避する
  • ⑤ description の二重指定
  • ⑥ コネクション判定は型名依存
  • 参考
  • 関連