JBuilder 教程:优雅地构建 JSON API
目录
- 什么是 JBuilder?
- 为什么选择 JBuilder?
- 环境准备
- 快速上手:你的第一个 JBuilder 视图
- 基本语法
- 渲染 JSON
- 核心概念详解
json/array- (Bang) 操作符
extract!partial(部分模板)
- 高级用法
- 条件渲染
- 循环与集合
- 自定义方法
- 处理关联数据
- 最佳实践与性能优化
- JBuilder vs. Jbuilder vs. Active Model Serializers
什么是 JBuilder?
JBuilder 是一个用于 Ruby on Rails 的模板引擎,它的唯一目的就是帮助你快速、简洁、可读性强地构建 JSON 响应。

你可以把它看作是 ERB (Embedded Ruby) 的 JSON 版本,在 ERB 中,你混合 HTML 和 Ruby 代码来生成 HTML 页面,在 JBuilder 中,你混合 JSON 语法和 Ruby 代码来生成 JSON 对象或数组。
它最初由 Shopify 的开发团队创建,后来被 Rails 社区广泛采用,是构建 RESTful API 的标准工具之一。
为什么选择 JBuilder?
相比于手动在控制器里用 render json: { ... } 拼接一个巨大的哈希,JBuilder 有以下显著优势:
- 关注点分离:JSON 的构建逻辑被封装在视图(
app/views/)中,而不是控制器里,控制器只负责数据查询和调用渲染,保持了代码的整洁。 - 可读性强:JBuilder 的 DSL(领域特定语言)非常直观,几乎就像在写 JSON 本身,代码更容易理解和维护。
- 复用性高:通过
partial(部分模板),你可以轻松复用 JSON 结构片段,避免代码重复。 - 功能强大:内置了条件渲染、循环、关联数据加载等高级功能,能优雅地处理复杂的 JSON 结构。
环境准备
-
确保你有一个 Rails 项目。
(图片来源网络,侵删) -
添加 JBuilder gem:如果你的 Rails 项目是新创建的,JBuilder 通常已经包含在
Gemfile的default组中,如果不是,请手动添加:# Gemfile gem 'jbuilder'
-
安装 gem:
bundle install
-
配置路由:确保你的 API 路由指向了相应的控制器。
快速上手:你的第一个 JBuilder 视图
假设我们有一个 Post 模型,我们想通过 API 返回一个帖子的 JSON 数据。

创建控制器和路由
# config/routes.rb
get '/posts/:id', to: 'posts#show'
# app/controllers/posts_controller.rb
class PostsController < ApplicationController
def show
@post = Post.find(params[:id])
end
end
创建 JBuilder 视图
在 app/views/posts/ 目录下,创建一个与控制器动作同名的 .jbuilder 文件。
# app/views/posts/show.jbuilder json.extract! @post, :id, :title, :content, :created_at
代码解释:
json.extract! @post, :id, :title, ...:这是 JBuilder 最核心的语法之一,它从@post对象中提取指定的属性,并将它们作为键值对添加到最终的 JSON 中。created_at会自动被格式化为 ISO 8601 标准的字符串。
启动服务器并访问
启动 Rails 服务器 (rails s),然后访问 http://localhost:3000/posts/1 (假设数据库中 ID 为 1 的帖子存在),你会得到类似这样的 JSON 响应:
{
"id": 1,: "Hello, JBuilder!",
"content": "This is my first JBuilder template.",
"created_at": "2025-10-27T10:30:00.123Z"
}
核心概念详解
json / array
JBuilder 视图的顶层可以是 json 对象或 array 数组。
对象(默认)
# app/views/posts/show.jbuilder json.id @post.id@post.title json.author do json.name @post.author.name json.email @post.author.email end
渲染结果:
{
"id": 1,: "A Great Post",
"author": {
"name": "John Doe",
"email": "john.doe@example.com"
}
}
数组
如果你想渲染一个集合,比如所有帖子列表。
# app/views/posts/index.jbuilder json.array! @posts do |post| json.extract! post, :id, :title json.author post.author.name end
渲染结果:
[
{
"id": 1,: "Post One",
"author": "Alice"
},
{
"id": 2,: "Post Two",
"author": "Bob"
}
]
(Bang) 操作符
在 JBuilder 中, (读作 "bang") 是一个强大的操作符,它有两个主要用途:
-
强制渲染:即使属性值为
nil或false,也会包含在 JSON 中。json.published @post.published # post.published 为 false,这个键值对会被忽略 json.published! @post.published # 即使为 false,也会渲染成 {"published": false} -
在
array!块中:作为json.array!块的最后一个参数,它会将块内生成的所有对象合并到一个数组中,而不是创建一个包含单个对象的数组的数组。# 不使用 ! json.array! @posts do |post| json.id post.id end # 结果: [{"id": 1}, {"id": 2}] # 使用 ! json.array! @posts, partial: "posts/post", as: :post, locals: { show_details: true } # 或者 json.array! @posts do |post| json.extract! post, :id end! # 结果: [{"id": 1}, {"id": 2}]
extract!
我们已经见过 extract!,它用于批量提取对象的属性。
json.extract! @post, :id, :title, :content
这比一个个写 json.id @post.id 要简洁得多。
partial (部分模板)
当你的 JSON 结构变得复杂,或者多个视图需要共享相同的 JSON 结构时,partial 就派上用场了。
创建部分模板
假设我们有 Post 和 Comment 模型,一个帖子有多个评论。
# app/views/posts/_post.jbuilder # 注意文件名以下划线开头,这是 Rails 的惯例 json.extract! post, :id, :title, :content json.author do json.name post.author.name end json.comments post.comments, partial: 'comments/comment', as: :comment
# app/views/comments/_comment.jbuilder json.extract! comment, :id, :body json.author comment.author.name
在主视图中使用
# app/views/posts/show.jbuilder
json.post partial: 'posts/post', locals: { post: @post }
渲染结果:
{
"post": {
"id": 1,: "Post with Comments",
"content": "Here are the comments.",
"author": {
"name": "Charlie"
},
"comments": [
{
"id": 101,
"body": "Great post!",
"author": "David"
},
{
"id": 102,
"body": "I agree.",
"author": "Eve"
}
]
}
}
高级用法
条件渲染
你可以使用 Ruby 的条件语句来控制 JSON 的生成。
json.extract! @post, :id, :title if @post.published? json.status "published" else json.status "draft" end # 更简洁的写法 json.status @post.published? ? "published" : "draft" # 使用 unless json.published_at @post.published_at unless @post.published_at.nil?
循环与集合
在 array! 块中,你可以自由地循环处理集合中的每个对象。
json.array! @posts do |post| json.id post.id json.title post.title json.comments_count post.comments.count json.tag_list post.tags.map(&:name) # 假设 post has_many :tags end
自定义方法
如果视图中的逻辑变得非常复杂,你可以将逻辑提取到模型或一个辅助模块中,然后在 JBuilder 中调用。
# app/models/post.rb
class Post < ApplicationRecord
def to_api_summary
{
id: id,
title: title,
summary: content.truncate(50)
}
end
end
# app/views/posts/index.jbuilder json.array! @posts do |post| # 调用模型中的自定义方法 json.merge! post.to_api_summary end
处理关联数据
当处理 has_many 或 has_one 关联时,最佳实践是在控制器中使用 includes 或 eager_load 来进行预加载,以避免 N+1 查询问题。
控制器中:
# app/controllers/posts_controller.rb def show @post = Post.includes(:author, :comments).find(params[:id]) end
JBuilder 视图中:
# app/views/posts/show.jbuilder json.extract! @post, :id, :title, :content json.author do json.extract! @post.author, :id, :name, :email end json.comments @post.comments, partial: 'comments/comment', as: :comment
最佳实践与性能优化
- 预加载关联:这是最重要的一点,始终在控制器中使用
includes来加载关联数据,否则 JBuilder 中的@post.author或@post.comments会导致在渲染 JSON 时发起额外的数据库查询。 - 保持视图简洁:JBuilder 视图应该只负责构建 JSON,复杂的业务逻辑(如计算、格式化)应该放在模型或服务对象中,然后从视图中调用。
- 使用
partial复用代码:不要在多个地方重复相同的 JSON 结构。 - 考虑缓存:对于不经常变化但频繁请求的 API 响应,可以考虑使用 Rails 的 fragment caching 或
jbuilder_cachegem 来缓存 JBuilder 输出。 - 版本控制:当 API 需要迭代时,创建新的版本(如
v1/posts/show.jbuilder)而不是修改现有版本,以确保向后兼容性。
JBuilder vs. Jbuilder vs. Active Model Serializers (AMS)
这是一个常见的混淆点:
- JBuilder:我们正在学习的这个库,是 Rails 社区的标准库,专注于简洁的模板语法。
- Jbuilder:这是一个拼写错误,通常指的就是 JBuilder。
- Active Model Serializers (AMS):一个功能更强大的库,它基于“序列化器”(Serializer)的概念,AMS 提供了更丰富的功能,如自定义类型转换、依赖加载、内置缓存等,但它的学习曲线比 JBuilder 稍陡峭,有时被认为更“重量级”。
如何选择?
- 对于大多数 Rails API:JBuilder 是一个绝佳的选择,它简单、直观,能满足 90% 的需求。
- 对于非常复杂或有特殊需求的 API:如果你的 API 需要高级特性(如自定义 JSON 类型、精细的依赖控制、复杂的缓存策略),可以考虑 AMS。
JBuilder 是一个优雅、高效且易于上手的工具,它能极大地提升你构建 Rails API 的开发体验,通过将 JSON 构建逻辑从控制器中分离出来,它让你的代码更加清晰、模块化和可维护。
希望这份教程能帮助你快速掌握 JBuilder!从今天开始,尝试用 JBuilder 来重构你的 API 吧。
