【免费下载链接】howtographql
The Fullstack Tutorial for GraphQL
本篇指南聚焦 howtographql 仓库中 Ruby / Rails 后端教程的核心章节——在 content/backend/graphql-ruby/8-pagination.md 中,你将基于已有的 Hackernews 风格的allLinks查询为 GraphQL API 添加limit-offset 分页:通过在 schema 中引入skip与first两个查询参数,让客户端按需获取一小批链接并逐页浏览更早的内容。读完本文,你将掌握在 graphql-ruby 项目中基于 SearchObject::Plugin::GraphQL 的 Resolver 模型,把LIMIT/OFFSET语义映射到 GraphQL 参数层的完整实现路径,并了解它与 Relay 游标分页(Connections)的本质区别及适用前提。
为什么 Hackernews 需要分页
随着用户不断提交链接,把所有历史上发布过的链接一次性全部拉取回来,很快就会变得"太多":不仅对客户端毫无用处,还会造成巨大的网络传输与内存开销。更合理的做法是:每次只展示少量链接,并允许用户通过翻页浏览更早的内容。
分页的核心价值在于把"数据量"的决定权交给客户端:客户端可以根据自己的屏幕尺寸、性能与交互场景,主动声明本次请求需要多少条记录、从哪个位置开始取。这正是 content/backend/graphql-ruby/0-introduction.md 中"Schema-Driven Development"所强调的契约精神——schema 是前后端约定的契约,分页参数的加入就是对这份契约的扩展,前端可以据此并行开发而无需等待后端完成。
本教程实现的分页方式称为limit-offset 分页。请注意:这种方式不适用于前端使用 Relay 的场景,因为 Relay 要求基于connections(连接)概念实现游标式分页。关于分页的更多背景,可参考 GraphQL 官方文档的 Pagination 章节。连接与 Relay 规范的其余部分可参考 Relay 官方文档。
目标 Schema:向allLinks注入skip与first
分页的第一步是定义 schema。我们希望在现有的allLinks查询上追加两个参数,得到如下形态:
type Query { allLinks(filter: LinkFilter, skip: Int, first: Int): [Link!]! }first:返回前n条记录(对应 SQL 的LIMIT n);skip:跳过前n条记录后再返回(对应 SQL 的OFFSET n)。
LinkFilter是 content/backend/graphql-ruby/7-filtering.md 中定义的输入类型,用于对链接进行description_contains/url_contains以及OR分支组合过滤。skip与first均为可选的Int类型——不传时表示"不限制",这与原文档及LinksSearchResolver 的option声明保持一致。
在LinksSearchResolver 中接入分页参数
allLinks查询由Resolvers::LinksSearch这一 SearchObject Resolver 承担解析工作(见 content/backend/graphql-ruby/7-filtering.md)。分页参数正是通过它的option机制注册进去的。更新app/graphql/resolvers/links_search.rb:
require 'search_object' require 'search_object/plugin/graphql' class Resolvers::LinksSearch # ...code option :filter, type: LinkFilter, with: :apply_filter option :first, type: types.Int, with: :apply_first option :skip, type: types.Int, with: :apply_skip def apply_first(scope, value) scope.limit(value) end def apply_skip(scope, value) scope.offset(value) end # ...code end改动完成!
SearchObject 的option机制是如何工作的
从源码结构看,SearchObject 的option声明做了三件事:
- 注册参数:
option :first, type: types.Int把first注册为一个 GraphQL 参数,类型映射为Int; - 指定处理函数:
with: :apply_first告诉 SearchObject,当客户端传入该参数时,调用对应的方法来加工当前的查询 scope; - 链式作用:
apply_first/apply_skip接收(scope, value),在 ActiveRecord 关系上链式调用limit/offset,与过滤、排序等其他 option 的处理结果自然串联,共同收窄最终的查询集。
由于scope { Link.all }是搜索的起点(见 content/backend/graphql-ruby/7-filtering.md),当客户端同时传入filter、skip、first时,执行顺序为:先按LinkFilter条件过滤得到子集,再offset(skip)跳过前面的记录,最后limit(first)截取返回数量。这种"以 scope 为管道的组合式处理"正是 SearchObject 相比手写 Resolver 更利于长期维护的原因——后续每新增一个查询维度,只需追加一个option与一个apply_*方法。
与QueryType的接线
在 content/backend/graphql-ruby/7-filtering.md 中,QueryType已经通过 resolver 方式挂载了all_links字段:
module Types class QueryType < BaseObject field :all_links, resolver: Resolvers::LinksSearch end end采用resolver:挂载后,schema 中字段的返回类型、参数集合均来自LinksSearch自身的声明(type types[Types::LinkType]加上各个option)。因此无需修改QueryType,skip与first便会自动出现在 GraphiQL 的字段文档与 Introsepction 结果中,与目标 schema 完全吻合。这正是 content/backend/graphql-ruby/2-queries.md 中所讲的两种字段解析方式之一:把解析逻辑封装进 GraphQL::Schema::Resolver,比在类型上写同名方法更利于复用与测试。
在 GraphiQL 中验证分页
启动 Rails 服务后,打开浏览器访问http://localhost:3000/graphiql,即可用如下查询验证:
query { allLinks(skip: 2, first: 3) { id url description } }该查询跳过前 2 条、只取接下来的 3 条链接。若省略skip/first,则与之前的allLinks行为一致,返回全部链接——参数的可选性保证了向后兼容。若first传入0或负数,limit/offset的语义需要结合具体数据库实现确认边界行为,因此建议客户端只传正整数。
分页的验证建议结合已有测试体系:在 content/backend/graphql-ruby/7-filtering.md 中展示了针对
LinksSearch的单元测试(test/graphql/resolvers/links_search_test.rb),通过::Resolvers::LinksSearch.call(nil, args, nil)直接调用 Resolver 并断言返回结果。你可以沿用同样的模式为first/skip编写用例,例如创建 4 条链接后断言find(skip: 1, first: 2)只返回中间两条。执行测试的命令为:
bundle exec rails testlimit-offset 与 Relay 游标分页的取舍
本教程选择 limit-offset 分页,有其明确的使用前提与边界:
| 维度 | limit-offset 分页 | Relay Connections(游标分页) |
|---|---|---|
| 参数 | first+skip | first/after(游标) |
| 实现成本 | 低,直接映射 SQLLIMIT/OFFSET | 较高,需要封装edges/node/cursor结构 |
| 前端配合 | 任意客户端 | 需要 Relay 或支持 Connections 规范的客户端 |
| 大数据量下的跳页 | OFFSET越大扫描开销越高 | 基于游标可稳定定位 |
| 本仓库适用范围 | ✅ 本教程(graphql-ruby 后端) | 前端教程中的 react-relay 路径 |
对应地,本仓库的其他后端教程也采用了同样的 limit-offset 思路:例如 content/backend/graphql-python/8-pagination.md 用 Python 切片qs[skip:]/qs[:first]实现,content/backend/graphql-java/10-pagination.md 用 MongoDB 的skip().limit()实现。可见"以first+skip两个参数收窄查询结果"是 GraphQL 服务端分页的通用实践,与具体语言无关。选择何种方案,取决于你的前端是否依赖 Relay 以及数据规模预期。
小结与后续方向
至此,allLinks查询同时具备了过滤(filter)与分页(skip、first)能力,一次查询即可完成"按条件筛选 + 分页取数"的组合需求。本教程的完整工程与更多改进可在 howtographql 系列的 Ruby 示例工程中查阅,其教程收尾见 content/backend/graphql-ruby/9-summary.md。
值得留意的进阶方向:
- 总条数统计:分页 UI 往往还需要"总共有多少条"信息,可在
meta/structure/ruby.md中看到_allLinksMeta: _QueryMeta!(含count字段)的设计参考,即额外暴露一个元数据查询用于返回计数; - 排序:
meta/structure/ruby.md中还预留了orderBy: LinkOrderBy(如createdAt_ASC/createdAt_DESC),可按同样方式注册为option; - 游标化升级:若未来需要对接 Relay 前端,可将 limit-offset 升级为基于 id 或创建时间的游标分页。
【免费下载链接】howtographql
The Fullstack Tutorial for GraphQL
相关推荐
Gatsby 分页实现指南:基于 GraphQL 的 limit/skip 分页实战
Gatsby 分页实现指南:基于 GraphQL 的 limit/skip 分页实战 导读:随着博客文章、商品列表等内容型页面的不断增长,单页列表会变得越来越长
前端静态站点Web框架掌握GORM排序与分页:Order/Limit/Offset终极实战指南
掌握GORM排序与分页:Order/Limit/Offset终极实战指南 GORM是Golang生态中一款开发者友好的ORM库,提供了简洁直观的API来处理数据
后端数据库ORMAuto Remove Torrents配置文件详解:打造个性化种子清理策略
Auto Remove Torrents配置文件详解:打造个性化种子清理策略 Auto Remove Torrents是一款强大的种子自动管理工具,能够根据用户
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考