投稿日:2026/8/5
更新日:2026/8/5

field は「GraphQL のスキーマに 1 行足す」DSL。シグネチャは以下。
field(name, type, description = nil, **options, &block)
実体は GraphQL::Schema::Field.new で、位置引数はすべてキーワードでも書ける(name: / type: / description:)。
以下は graphql 2.6.5 のソースを読んで確認した内容。
name → type → description。残りは全部キーワードnull: を省略すると true(nullable)。non-null にしたいときだけ null: false を書く[String] は [String!] になるmethod: / hash_key: / resolver_method: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。
type Order {
id: ID!
"""税込の合計金額(円)"""
totalPrice: Int!
note: String
items: [OrderItem!]!
}
Ruby 側は snake_case、スキーマ側は camelCase。この変換は camelize:(デフォルト true)が担当している。
namecamelize: true(デフォルト)で total_price → totalPrice に変換されるfield :total_price, Integer, null: false # totalPrice
field :total_price, Integer, null: false, camelize: false # total_price(スキーマ側もそのまま)
type型の書き方は 4 通りある。
| 書き方 | 例 | 備考 |
|---|---|---|
| Ruby の組み込みクラス | String / Integer / Float |
内部で GraphQL::Types::String などに変換される |
| graphql-ruby の定数 | ID / Int / Boolean |
実体は "ID" などの文字列定数(GraphQLTypeNames) |
| 型クラス | Types::OrderType |
自作の型 |
| 文字列 | "Types::OrderType" |
遅延解決。循環参照や読み込み順の問題を避けたいとき |
リストは配列で囲む。
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 で表現する。
field :name, String, null: false # name: String!
field :name, String # name: String
省略時のデフォルトは null: true。ソース上も null が nil のときは true にフォールバックしている。
ただし resolver: を使う場合だけは、省略するとリゾルバ側の null 設定を引き継ぐ。
description位置引数でも description: キーワードでも書ける。両方書くと ArgumentError。
field :total_price, Integer, '税込の合計金額(円)', null: false # 位置引数
field :total_price, Integer, null: false, description: '税込の合計金額(円)' # キーワード
ブロック内で description を呼ぶ形もある。
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 ..."]
つまり普段は「④ モデルのメソッドがそのまま呼ばれている」だけ。型クラスに同名メソッドを定義すると、そちらが優先される。
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: |
固定値 | メソッドが無くても落としたくない |
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 を並べる。
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_id → xxx) |
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: |
__ 始まりの名前を許可する内部フィールド |
# 非推奨化
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 で決まる。全フィールド共通の振る舞い(認可、ログ、独自オプション)はここに置く。
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
field :internal_memo, String, permission: :admin
field は知らないキーワードを渡すと ArgumentError になるので、独自オプションは必ず BaseField#initialize で受け取ってから super に渡す。
null: を書き忘れると nullable になる昔のバージョンは null: が必須だったが、現在は省略可= true 扱い。「書き忘れ」が黙って nullable なスキーマになる。
non-null を基本にしたいなら BaseField でデフォルトを反転させるか、rubocop / スキーマの差分レビューで気づける状態にしておく。
field :tags, [String], null: false # [String!]!
「配列自体も要素も non-null」になる。要素に nil が混ざると実行時エラーになるので、compact 漏れがあると本番で落ちる。
要素に nil があり得るなら [String, null: true]。
field :method や field :object のように、Object のインスタンスメソッド・Ruby の予約語・graphql-ruby の予約語(context / object / raw_value)と同名だと、定義時に警告が出る。
field :object, Types::OrderType, null: false, resolver_method: :resolve_object
def resolve_object
object.target
end
resolver_method: を指定すると警告も消える。どうしても黙らせたいだけなら method_conflict_warning: false。
OrderType が UserType を、UserType が OrderType を参照するようなケースでは、定数解決の順番で uninitialized constant になることがある。型名を文字列で書くと遅延解決になる。
field :user, "Types::UserType", null: false
定数の探索順そのものは 定数の参照とダブルコロン(スコープ解決演算子) を参照。
description の二重指定位置引数とキーワードの両方を書くと ArgumentError。リファクタで片方を消し忘れると定義時に落ちる(実行時ではないので気づきやすい)。
connection: を明示しない場合、返り値の型名が Connection で終わるかだけで判定される。独自のページネーション型に Connection という名前を付けると、意図せず Relay の引数(first / after など)が生える。
Types::Enum::X の解決と autoloadextras: [:lookahead] で先読みする話の前提