name: implement-graphql type: atomic license: MIT description: > Use when building or reviewing GraphQL APIs in Rails with graphql-ruby — must follow the TDD gates by writing a failing spec in spec/graphql/ using AppSchema.execute rather than HTTP controller dispatch, define arguments/return types without leaking internal model names (use connection_type for pagination), implement resolver/mutation classes that delegate to services, prevent N+1 queries by using and priming the dataloader on association loads, and ensure mutations return result and errors shapes on failure. Trigger words: graphql, graphql-ruby, resolver, mutation, dataloader, schema. metadata: version: 1.0.0 user-invocable: "true"
Implement GraphQL
Use this skill when designing, implementing, or reviewing GraphQL APIs in a Rails application with the graphql-ruby gem.
Core Process
DO NOT proceed to step 3 before step 1 is written and failing.
SPEC: Write failing spec (happy path + auth + validation error case) — see TESTING.md. Use
AppSchema.executeinspec/graphql/. Never use HTTP controller dispatch for GraphQL specs.TYPE: Define arguments and return types. Use
connection_typefor pagination shapes. Do not leak internal model names.IMPLEMENT: Create resolver/mutation class delegating to a service object. Use dedicated classes instead of inline field blocks.
N+1 CHECK: Use dataloader on every association load. For list resolvers, prime the dataloader with the records returned by the relation before fields resolve associated objects. Use
bulletanddb-query-matchersin specs.# ✅ batches loads across all records def buyer dataloader.with(Sources::RecordById, Buyer).load(object.buyer_id) endAUTH CHECK: Apply field-level guards where data is sensitive using Pundit or custom context guards.
field :internal_notes, String, null: true do guard -> (_obj, _args, ctx) { ctx[:current_user]&.admin? } endFINAL CHECK: Verify every item in the HARD-GATE checklist below. Ensure mutations return
{ result, errors }shapes on failure.rescue ActiveRecord::RecordInvalid => e { order: nil, errors: e.record.errors.full_messages }RUN: Ensure the full test suite is green before PR.
HARD-GATE Checklist
Before shipping a resolver/mutation slice, ALL of the following must be confirmed:
- Specs — covers happy path, unauthenticated, unauthorized, validation errors, N+1 counts, and schema limits.
- N+1 Prevention —
dataloader.with(Source, Model).load(id)on every association; neverobject.association. - Dataloader Priming — collection resolvers prime records before association fields resolve.
- Authorization — sensitive fields have field-level guards (not type-level alone).
- Type Conventions — paginated collections use
Types::*Type.connection_type, not plain arrays. - Schema Safeguards — introspection disabled in production;
max_depthandmax_complexityset. - Error Handling — mutations return
{ result, errors }with rescue blocks; no unhandled exceptions. - Documentation —
description:on every field in every type. - Resolver Structure — dedicated resolver classes, not inline field blocks.
Extended Resources (Progressive Disclosure)
Load these files only when their specific content is needed:
- TESTING.md — For the spec template, paths, and checklist.
- EXAMPLES.md — For detailed code examples of dataloaders, mutations, and types.
Integration
| Skill | When to chain |
|---|---|
| define-domain-language | Type and field naming must match business language |
| plan-tests | Choose first failing spec (mutation vs query vs resolver unit) |
| write-tests | Full TDD cycle for resolvers and mutations |
| security-check | Auth, introspection disable, query depth/complexity limits |