zammad/.dev/agent_docs/graphql_patterns.md
Dominik Klein 90a305de04 Maintenance: Add some coding AI-Agent instruction improvements.
Co-authored-by: Dusan Vuckovic <dv@zammad.com>
Co-authored-by: Dominik Klein <dk@zammad.com>
Co-authored-by: Florian Liebe <fl@zammad.com>
Co-authored-by: Benjamin Scharf <bs@zammad.com>
2026-03-23 22:16:08 +01:00

1.6 KiB

GraphQL Patterns

Resolver Structure

Resolvers are thin wrappers. Business logic lives in app/services/service/:

def resolve(...)
  Service::SomeAction.new(...).execute
end

Authorization

Multiple layers, applied declaratively:

  • requires_permission('ticket.agent') — static permission check at resolver level
  • allow_public_access! — opt-out of authentication
  • loads_pundit_method: :update? — authorize loaded objects via Pundit at argument level
  • requires_enabled_setting('some_feature') / requires_disabled_setting(...) — feature flag guards

Object-level authorization uses Pundit via HasPunditAuthorization concern on types.

Auto-Registration

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.

Custom Relation DSL

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

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

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.