rails-graphql-patterns
Analyzes and recommends GraphQL patterns for Rails using graphql-ruby including schema design, types, resolvers, mutations, subscriptions, DataLoader, and query complexity. Use when building GraphQL APIs, defining types, writing mutations, optimizing N+1 queries, or structuring app/graphql. NOT for REST API controllers, ActiveRecord queries outside GraphQL, or Turbo Stream responses.
Works with
---
name: rails-graphql-patterns
description: Analyzes and recommends GraphQL patterns for Rails using graphql-ruby including schema design, types, resolvers, mutations, subscriptions, DataLoader, and query complexity. Use when building GraphQL APIs, defining types, writing mutations, optimizing N+1 queries, or structuring app/graphql. NOT for REST API controllers, ActiveRecord queries outside GraphQL, or Turbo Stream responses.
license: MIT
---
# Rails GraphQL Patterns
Analyze and recommend patterns for building GraphQL APIs in Rails with graphql-ruby.
Follow standard graphql-ruby conventions for type definitions, input types, enum types, query types, and basic mutations. This skill focuses on non-obvious patterns and opinionated decisions.
## Core Principles
1. **Batch loading**: Always use DataLoader to prevent N+1 queries — never resolve associations directly
2. **Structured errors**: Return error arrays in mutation payloads, not exceptions
3. **Field-level authorization**: Check permissions per field, not just per query
4. **Complexity limits**: Set `max_complexity` and `max_depth` on the schema
5. **Null annotations**: Be intentional with `null: true/false` on every field
## DataLoader for N+1 Prevention
Every association field MUST use DataLoader. Never call `object.association` directly in a resolver.
```ruby
class Sources::RecordLoader < GraphQL::Dataloader::Source
def initialize(model_class, column: :id)
@model_class = model_class
@column = column
end
def fetch(ids)
records = @model_class.where(@column => ids).index_by { |r| r.send(@column) }
ids.map { |id| records[id] }
end
end
# Usage in type
def author
dataloader.with(Sources::RecordLoader, User).load(object.user_id)
end
```
See [patterns.md](patterns.md) for AssociationLoader and CountLoader variants.
## Connection Types (Pagination)
Always use connection types for list fields — never return unbounded arrays.
```ruby
module Types
class PostConnectionType < GraphQL::Types::Connection
edge_type(Types::PostEdgeType)
field :total_count, Integer, null: false
def total_count
object.items.size
end
end
end
# In query type
field :posts, Types::PostConnectionType, null: false, connection: true do
argument :filter, Types::PostFilterInput, required: false
end
```
## Subscriptions — Triggering from Models
```ruby
# Trigger from model after_create_commit, not from mutations
class Post < ApplicationRecord
after_create_commit :notify_subscribers
private
def notify_subscribers
MyAppSchema.subscriptions.trigger(:post_created, {}, self)
end
end
```
## Schema Configuration
```ruby
class MyAppSchema < GraphQL::Schema
max_complexity 300
max_depth 15
default_max_page_size 25
use GraphQL::Dataloader
rescue_from ActiveRecord::RecordNotFound do |_err, _obj, _args, _ctx, field|
raise GraphQL::ExecutionError, "#{field.type.unwrap.graphql_name} not found"
end
end
```
Per-field complexity for expensive operations:
```ruby
field :expensive_field, String do
complexity 50
end
```
## Anti-Patterns
| Anti-Pattern | Fix |
|-------------|-----|
| N+1 in resolvers | Use DataLoader / Sources |
| No complexity limits | Set `max_complexity` and `max_depth` |
| Raising exceptions in mutations | Return `{ errors: [...] }` in payload |
| Authorization only at query root | Check auth at field level |
| Exposing ActiveRecord directly | Define explicit GraphQL types |
| Fat resolvers with business logic | Delegate to service objects |
| No pagination on list fields | Use connection types |
## Output Format
When analyzing or creating GraphQL components, provide:
1. **Type/mutation file** with proper null annotations
2. **DataLoader source** if associations are resolved
3. **Schema configuration** (complexity, depth, pagination)
4. **Test outline** executing queries against the schema
5. **Authorization** strategy (context, field-level checks)
## Error Handling
- Mutations return `{ resource: nil, errors: [...] }` on validation failure
- Use `rescue_from` in schema for `RecordNotFound` mapped to `GraphQL::ExecutionError`
- Field-level: return `nil` or raise `GraphQL::ExecutionError` for unauthorized access
- Never expose internal error details to clients in productionMore API Design skills
lark-event
larksuite/cli
Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses.
lark-contact
larksuite/cli
飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名,以及按关键词搜索当前用户可见的机器人 / 智能体(agent)。当用户提到一个名字要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。
lark-openapi-explorer
larksuite/cli
飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。

