.dev/agent_docs/graphql_patterns.md
Resolvers are thin wrappers. Business logic lives in app/services/service/:
def resolve(...)
Service::SomeAction.new(...).execute
end
Multiple layers, applied declaratively:
requires_permission('ticket.agent') — static permission check at resolver levelallow_public_access! — opt-out of authenticationloads_pundit_method: :update? — authorize loaded objects via Pundit
at argument levelrequires_enabled_setting('some_feature') /
requires_disabled_setting(...) — feature flag guardsObject-level authorization uses Pundit via HasPunditAuthorization
concern on types.
Queries, mutations, and subscriptions are auto-registered — no manual field registration needed. Just create the class inheriting from the appropriate base and it will be discovered.
Types use custom macros (not standard graphql-ruby):
belongs_to :group, Gql::Types::GroupType
has_one :organization, Gql::Types::OrganizationType
These automatically set up batch-loaded resolvers and mark fields as dependent for permission checks.
scoped_fields do
field :some_field, String, null: true
end
Fields inside scoped_fields are restricted by Pundit FieldScope —
they return nil instead of errors when unauthorized. Must be nullable.
internal_fields do
field :some_field, String
end
Fields inside internal_fields are restricted by checking for admin/agent permission.
They return nil instead of errors when unauthorized.