RABL(Ruby API Builder Language)是一个用于生成 JSON、XML、MessagePack、PList 和 BSON 的 Rails 和 Padrino Ruby 模板系统。在使用 ActiveRecord 的 'to_json' 方法时,我发现自己渴望一个更具表现力和更强大的 API 生成方案。当 JSON 表示复杂或与数据库中定义的确切模式不匹配时,这一点尤为突出。
特别是,我希望能够轻松实现:
任何尝试过使用 ActiveRecord 的 'to_json' 方法生成 JSON 响应的人都体会过这种受限方法的痛苦。RABL 是一个通用的模板系统,通过以全新的方式处理 API 响应生成,旨在解决这些问题。
RABL 的核心是坚持 MVC 原则,将 API 数据表示推迟到应用程序的视图层。关于对 RABL 常见误解的详细说明,请查看我们的指南: 理解 RABL,这有助于澄清关于此项目的任何混淆。
v0.8.0(2013年2月14日发布)移除了 multi_json 依赖, 转而依赖 Oj(或 JSON)作为 JSON 解析器。简化了代码,移除了一个依赖, 但你可能需要移除对 MultiJson 的任何引用。
v0.6.14(2012年6月28日发布)要求使用 render_views 与 RSpec 一起测试模板。否则,控制器只会 像处理 ERB 模板一样直接传递 render 命令。
以 gem 方式安装 RABL:``` gem install rabl
或者添加到你的Gemfile中:```ruby
# Gemfile
gem 'rabl'
# Also add either `oj` or `yajl-ruby` as the JSON parser
gem 'oj'
然后运行 bundle install 来安装依赖。
如果你使用的是 Rails 2.3.8(及更高版本)、Rails 3.X 或 Padrino,RABL 无需额外配置即可正常工作。
重要提示: 在 Padrino 中,请确保 rabl gem 在 Gemfile 中列于 padrino gem 之后,否则 Rabl 将不会正确注册为模板引擎。
对于 Sinatra 或任何其他基于 tilt 的框架,只需注册:```ruby Rabl.register!
RABL 将被初始化并准备好使用。关于在 Sinatra 中的使用,请查阅
[Sinatra 用法](https://github.com/nesquena/rabl/wiki/Setup-for-Sinatra) 指南。
## 概述 ##
你可以使用 RABL 从任何 Ruby 对象生成基于 JSON 和 XML 的 API。
使用 RABL,数据通常主要来自模型(ORM 无关),并且 API 输出的表示形式在视图模板中使用简单的 Ruby DSL 描述。这使你可以将数据与你希望输出的 JSON 或 XML 分离。
一旦你安装了 RABL(如上所述),你可以构建一个 RABL 视图模板,然后非常容易地从你的 Sinatra、Padrino 或 Rails 应用程序的控制器(或路由)中渲染该模板。以 [Padrino](http://padrinorb.com) 为例,假设你有一个包含博客文章的 `Post` 模型,你可以通过创建一个路由来渲染 API 表示(JSON 和 XML):```ruby
# app/app.rb
get "/posts", :provides => [:json, :xml] do
@user = current_user
@posts = Post.order("id DESC")
render "posts/index"
end
然后我们可以创建以下RABL模板来表达@posts的API输出:```ruby
collection @posts attributes :id, :title, :subject child(:user) { attributes :full_name } node(:read) { |post| post.read_by?(@user) }
访问 `http://localhost:3000/posts.json` 时将会输出以下 JSON 或 XML```js
[{ "post" :
{
"id" : 5, title: "...", subject: "...",
"user" : { full_name : "..." },
"read" : true
}
}]
这是一个基本概述,但还有更多内容可以查看,比如局部模板、继承、自定义节点等。请阅读下面关于 RABL 的完整细节。
RABL 的设计目标是几乎不需要配置即可运行。在大多数情况下确实如此,但根据你的需求,你可能需要在应用程序中设置以下全局配置(这个代码块完全是可选的):```ruby
require 'rabl' Rabl.configure do |config|
end
每个选项指定了与 RABL 输出相关的行为。如果禁用了 `include_json_root`,则会移除输出中每个根对象的根节点;而 `enable_json_callbacks` 如果启用,当传入请求包含 'callback' 参数时,支持 'jsonp' 风格的回调输出。
如果 `include_child_root` 设置为 false,则响应中的子对象默认不会包含根节点。这允许您进一步微调所需的响应结构。
如果设置了 `cache_engine`,则应将其赋给一个具有 `fetch` 方法的类。有关示例,请参阅[默认引擎](https://github.com/nesquena/rabl/blob/master/lib/rabl/cache_engine.rb)。
如果 `perform_caching` 设置为 `true`,则会执行缓存。如果使用 Rails,可以忽略此选项,它与 Rails 的 `config.action_controller.perform_caching` 相同。
如果 `cache_sources` 设置为 `true`,则模板查找将被缓存以提高性能。可以通过在应用程序中运行 `Rabl.reset_source_cache!` 手动重置缓存。
如果 `cache_all_output` 设置为 `true`,则每个模板(包括作为集合一部分使用的每个单独模板)都将被单独缓存。此外,child、glue 和 partial 中的任何内容也会被单独缓存。如果只想缓存单个模板,请参阅下面的“Caching”部分。
如果 `escape_all_output` 设置为 `true` 并且 ActiveSupport 可用,则属性输出将使用 [ERB::Util.html_escape](http://corelib.rubyonrails.org/classes/ERB/Util.html) 进行转义。自定义节点不会被转义,请使用 `ERB::Util.h(value)`。
如果 `view_paths` 设置为一个路径,则该视图路径将在应用程序中的每个 rabl 模板中被检查。尤其是在将 Rabl 包含在引擎中并在另一个 Rails 应用中使用视图路径时,添加到该路径。
如果 `raise_on_missing_attribute` 设置为 `true`,则每当 Rabl 尝试渲染不存在的属性时,将引发 RuntimeError。否则,该属性将被简单地忽略。在开发过程中将其设置为 true 可能有助于提高代码的健壮性,但不建议在生产代码中使用 `true`。
如果 `replace_nil_values_with_empty_strings` 设置为 `true`,则所有原本在响应中显示为 `null` 的 `nil` 值将被转换为空字符串。
如果您希望使用 [oj](https://github.com/ohler55/oj) 作为主要的 JSON 编码引擎,只需将其添加到您的 Gemfile 中:```ruby
# Gemfile
gem 'oj'
而 RABL 将自动使用该引擎来编码您的 JSON 响应。
设置您自己的自定义 json_engine,它定义一个 dump 或 encode 方法,用于从 Ruby 数据转换为 JSON 格式:```ruby
config.json_engine = ActiveSupport::JSON
### 格式配置 ###
RABL 支持 MessagePack、BSON 和 Plist 的配置。更多详情请查看
[格式配置](https://github.com/nesquena/rabl/wiki/Configuring-Formats)页面。
## 用法 ##
### 对象分配 ###
声明模板中使用的数据对象:```ruby
# app/views/users/show.json.rabl
object @user
或为对象指定别名:```ruby object @user => :person
或传递一个对象集合:```ruby
collection @users
# => [ { "user" : { ... } } ]
或为集合指定一个根节点标签:```ruby collection @users => :people
或者甚至可以为一个集合同时指定子标签和根标签:```ruby
collection @users, :root => "people", :object_root => "user"
# => { "people" : [ { "user" : { ... } } ] }
并且这将用作渲染的默认数据,或者显式禁用对象根:```ruby collection @users, :root => "people", :object_root => false
仅存在特殊情况,其中响应的根级不直接映射到任何对象:```ruby
object false
node(:some_count) { |m| @user.posts.count }
child(@user) { attribute :name }
在这些情况下,可以将对象赋值为'false',并且可以自由构建节点。
模板器的基本用法是为响应定义一些简单的属性:```ruby
attributes :id, :foo, :bar
或使用别名属性:```ruby
# Take the value of model attribute `foo` and name the node `bar`
attribute :foo => :bar
# => { bar : 5 }
甚至多个别名字段:```ruby attributes :bar => :baz, :dog => :animal
或者仅在条件为真时显示属性:```ruby
# m is the object being rendered, also supports :unless
attributes :foo, :bar, :if => lambda { |m| m.condition? }
命名属性和别名属性不能在同一行组合。目前这不起作用:```ruby attributes :foo, :bar => :baz # throws exception
### Child Nodes ###
通常,响应需要包含与父模型关联的数据中的嵌套信息:```ruby
child :address do
attributes :street, :city, :zip, :state
end
您也可以对子节点禁用对象根:```ruby child :posts, :object_root => false do attributes :id, :title end
您也可以从任意数据源添加子节点:```ruby
child @posts => :foobar do
attributes :id, :title
end
或者使用带有别名的模型关联:```ruby
child :posts => :foobar do attributes :id, :title end
您也可以传入当前对象:```ruby
object @user
child :posts do |user|
attribute :title unless user.suspended?
end
你也可以将子属性附加回根节点:```ruby
glue @post do attributes :id => :post_id, :name => :post_name end
使用 glue 为父对象添加额外属性。
你也可以传入当前对象:```ruby
object @user
glue(@post) {|user| attribute :title if user.active? }
这将根据 node 块的结果生成一个 json 响应:```ruby
node :full_name do |u| u.first_name + " " + u.last_name end
或者一个仅在条件为真时才存在的自定义节点:```ruby
# m is the object being rendered, also supports :unless
node(:foo, :if => lambda { |m| m.has_foo? }) do |m|
m.foo
end
或者不传递名称,而是将节点块合并到响应中:```ruby node do |u| { :full_name => u.first_name + " " + u.last_name }
end
您可以使用像这样的自定义节点,利用模型中的所有数据来创建灵活的值表示。
### Partials ###
通常,您需要访问其他数据对象,以便在更复杂的关联中构建自定义节点。您可以通过渲染RABL partial来获取另一个数据对象的rabl表示:```ruby
node :location do
{ :city => @city, :address => partial("users/address", :object => @address) }
end
或者甚至访问与父模型关联的对象:```ruby node :location do |m| { :city => m.city, :address => partial("users/address", :object => m.address) } end
你可以使用此方法为你的API构建任意复杂的节点。请注意,你需要为每个希望以这种方式构建表示的对象定义RABL模板。
### Inheritance ###
许多模板构建器的另一个常见问题是代码冗余。通常,一个对象在多个端点中的表示会共享共同的属性或节点。'post'对象的节点在各个端点的大部分引用中可能相同或相似。
RABL能够扩展其他"base"rabl模板及附加属性:```ruby
# app/views/users/advanced.json.rabl
extends "users/base" # another RABL template in "app/views/users/base.json.rabl"
node :can_drink do |m|
m.age > 21
end
您还可以在构造 child nodes 时扩展其他 rabl 模板以减少重复代码:```ruby
child @address do extends "address/item" end
使用局部模板和继承可以显著减少模板中的代码重复。
你可以在[重用模板维基页面](https://github.com/nesquena/rabl/wiki/Reusing-templates)上看到更多示例。
### 在局部模板中传递本地变量 ###
在渲染局部模板或继承模板时,你可以传递任意一组本地变量。
例如,如果我们想在`posts/:id.json`上显示关于特定帖子和关联评论的任何信息,但在其他情况下隐藏这些评论。我们可以使用本地变量来实现:```ruby
# app/views/posts/index.json.rabl
collection @posts
extends('posts/show', :locals => { :hide_comments => true })
# or using partial instead of extends
# node(false) { |post| partial('posts/show', :object => :post, :locals => { :hide_comments => true })}
然后访问子模板中的局部变量:```ruby
object @post
attributes :id, :title, :body, :created_at node(:comments) { |post| post.comments } unless locals[:hide_comments]
这在扩展或渲染部分时,可以作为高级工具使用。
### 模板作用域 ###
在 RABL 中,你可以访问构建 API 响应所需的一切。每个 RABL 模板都可以完全访问控制器的实例变量,以及所有视图助手和路由 URL。```ruby
# app/some/template.rabl
object @post
# Access instance variables
child(@user => :user) { ... }
# or Rails helpers
node(:formatted_body) { |post| simple_format(post.body) }
获取适当数据来构建响应应该没有问题。
在API中,通常需要构建第二层或第三层节点。假设我们有一个'quiz'模型,它有很多'questions',每个问题又有很多'answers'。我们可以很轻松地在RABL中显示这种层次结构:```ruby
object @quiz attribute :title child :questions do attribute :caption child :answers do # Use inheritance to reduce duplication extends "answers/item" end end
这将显示具有嵌套问题和答案的测验对象,正如您对测验节点所期望的那样,以及嵌入的问题和答案。
请注意,RABL 可以在子节点内任意深度嵌套,以允许定义这些表示形式。
### Caching ###
RABL 内置了利用片段缓存策略的模板缓存支持。请注意,当前缓存**仅适用于** Rails,但计划在未来版本中支持其他框架。最简单的缓存用法是:```ruby
# app/views/users/show.json.rabl
object @quiz
cache @quiz # key = rabl/quiz/[cache_key]
attribute :title
在生产环境中,缓存可以显著加速RABL模板的渲染,强烈建议在可能的情况下使用。关于缓存的更多细节,请查看维基上的 Caching 指南。
有些情况下,应用程序需要在传统视图上下文之外渲染RABL模板。例如,在Rake任务中渲染RABL或创建消息队列负载。在这种情况下,可以如下使用 Rabl.render:```ruby
Rabl.render(object, template, :view_path => 'app/views', :format => :json) #=> "{...json...}"
你还可以使用 `Rabl::Renderer` 的便捷方法来渲染对象:```ruby
Rabl::Renderer.json(@post, 'posts/show')
Rabl::Renderer.xml(@post, 'posts/show')
这些方法允许RABL用于将对象任意转换为所需格式。```ruby Rabl::Renderer.new('posts/show', @post, :view_path => 'app/views', :format => 'hash').render
您还可以传入其他实例变量以在模板中使用,例如:```ruby
Rabl::Renderer.new('posts/show', @post, :locals => { :custom_title => "Hello world!" })
然后,在你的模板中,你可以使用 @custom_title 作为:```
attribute :content
node(:title) { @custom_title }
### Content Type Headers ###
目前,在 RABL 中,响应的 content-type 不会自动设置。这是因为 RABL 旨在兼容任何基于 Rack 的框架,并尽可能保持格式无关性。
查看 [这个 issue](https://github.com/nesquena/rabl/issues/185#issuecomment-4501232) 了解更多详情。如果你有任何想法或补丁,请告诉我。
在此期间,如果需要,请确保设置了正确的 content-type。在 Rails 和 Padrino 中,这通常非常简单。我建议在该控制器中使用 before_filter,或者直接在操作中指定。
## Resources ##
有许多与 RABL 相关的资源,包括 [RABL Wiki](https://github.com/nesquena/rabl/wiki) 以及下面详细介绍的许多教程和指南。
你也可以查看 [RABL 网站](http://nesquena.github.com/rabl)。
### Advanced Usage ###
高级用法相关资源链接:
* [管理复杂性](https://github.com/nesquena/rabl/wiki/Managing-complexity-with-presenters)
* [生产优化](https://github.com/nesquena/rabl/wiki/Rabl-In-Production)
* [Grape 集成](https://github.com/nesquena/rabl/wiki/Using-Rabl-with-Grape)
* [使用 RABL 为树结构渲染 JSON](https://github.com/nesquena/rabl/issues/70)
* [RABL 中的布局(erb、haml 和 rabl)](https://github.com/nesquena/rabl/wiki/Using-Layouts)
* [Backbone 或 Ember.js 集成](https://github.com/nesquena/rabl/wiki/Backbone-Integration)
* [RABL 与 Rails Engines](https://github.com/nesquena/rabl/wiki/Setup-rabl-with-rails-engines)
请添加你自己的用法并告诉我,以便我们将其加入列表!同时,请务必查看 [RABL Wiki](https://github.com/nesquena/rabl/wiki) 了解其他用法。
### Tutorials ###
刚开始使用时,教程总是很有帮助:
* [Railscasts #322](http://railscasts.com/episodes/322-rabl) - Ryan Bates 解释 RABL
* [BackboneRails](http://www.backbonerails.com/) - Brian Mann 的精彩截屏
* [使用 RABL 和 Padrino 创建 API](http://blog.crowdint.com/2012/10/22/rabl-with-padrino.html)
* http://blog.joshsoftware.com/2011/12/23/designing-rails-api-using-rabl-and-devise/
* http://engineering.gomiso.com/2011/06/27/building-a-platform-api-on-rails/
* http://blog.lawrencenorton.com/better-json-requests-with-rabl
* http://www.rodrigoalvesvieira.com/developing-json-api-rails-rabl/
* http://tech.favoritemedium.com/2011/06/using-rabl-in-rails-json-web-api.html
* http://seesparkbox.com/foundry/better_rails_apis_with_rabl
* http://blog.dcxn.com/2011/06/22/rails-json-templates-through-rabl
* http://teohm.github.com/blog/2011/05/31/using-rabl-in-rails-json-web-api
如果还有此处未列出的其他有用资源,请告知。
### Related Libraries ###
还有其他一些库可以补充或扩展 RABL 的功能:
* [versioncake](https://github.com/bwillis/versioncake) - 用于轻松对 RABL API 进行版本控制的优秀库
* [gon](https://github.com/gazay/gon) - 将 Rails 变量暴露给 JavaScript,并内置 RABL 支持。
* [rabl-rails](https://github.com/ccocchi/rabl-rails) - RABL 和 Rails 的重新实现,
[专注于速度](https://github.com/ccocchi/rabl-benchmark/blob/master/BENCHMARK)。
如果还有此处未列出的其他相关库,请告知。
### Troubleshooting ###
* [集合的冗余调用](https://github.com/nesquena/rabl/issues/142#issuecomment-2969107)
* [测试 RABL 视图](https://github.com/nesquena/rabl/issues/130#issuecomment-4179285)
### Examples ###
请参见 [examples](https://github.com/nesquena/rabl/tree/master/examples) 目录。
## Issues ##
查看 [Issues](https://github.com/nesquena/rabl/issues) 标签页获取完整列表:
* 严格的基准测试和性能优化
## Authors and Contributors ##
感谢 [Miso](http://gomiso.com) 允许我为我们的应用程序创建该项目并发布!
* [Nathan Esquenazi](https://github.com/nesquena) - 项目创建者
* [Arthur Chiu](https://github.com/achiu) - 核心维护者,Riot 测试专家
* [Tim Lee](https://github.com/timothy1ee) - RABL 这个好名字由 Miso CTO 选定。
* [David Sommers](https://github.com/databyte) - 模板解析、缓存支持以及更多功能
* [Rick Thomas](https://github.com/rickthomasjr) - 增加了 extends 选项和 Sinatra 测试
* [Benjamin Yu](https://github.com/byu) - 增加了 msgpack 格式支持
* [Chris Kimpton](https://github.com/kimptoc) - 帮助完善文档和 wiki
* [Marjun](https://github.com/mpagalan) - 增加了 xml 选项配置
* [Anton Orel](https://github.com/skyeagle) - 增加了 Rails 3.1 兼容性
* [Sasha Koss](https://github.com/kossnocorp) - 增加了 multi_json 支持
* [Matthew Schulkind](https://github.com/mschulkind) - 清理配置和测试
* [Luke van der Hoeven](https://github.com/plukevdh) - 在模板中支持非 ORM 对象
* [Andrey Voronkov](https://github.com/Antiarchitect) - 增加了 BSON 格式支持
* [Alli Witheford](https://github.com/alzeih) - 增加了 Plist 格式支持
* [Ryan Bigg](https://github.com/radar) - 改进了模板解析代码
* [Ivan Vanderbyl](https://github.com/ivanvanderbyl) - 增加了通用渲染器
* [Cyril Mougel](https://github.com/shingara) - 增加了 cache_engine 可插拔支持和渲染器调整
* [Teng Siong Ong](https://github.com/siong1987) - 改进了渲染器接口
* [Brad Dunbar](https://github.com/braddunbar) - 将当前对象传递给块
以及更多贡献者,详见 [CHANGELOG](https://github.com/nesquena/rabl/blob/master/CHANGELOG.md)。
想要贡献对其他格式的支持?
请参考 [msgpack 支持](https://github.com/nesquena/rabl/pull/69)、[plist 支持](https://github.com/nesquena/rabl/pull/153) 和 [BSON 支持](https://github.com/nesquena/rabl/pull/163) 的补丁。
欢迎 fork 和贡献,任何帮助改善此项目的努力都值得感激!
该项目是 [OSS Manifesto](http://ossmanifesto.org) 的成员。
## Inspirations ##
有几个优秀的库启发了 RABL,它们列在下面:
* [Tequila](https://github.com/inem/tequila)
* [JSON Builder](https://github.com/dewski/json_builder)
* [Argonaut](https://github.com/jbr/argonaut)
再次感谢所有这些出色的项目。
## Copyright ##
版权所有 © 2011-2012 Nathan Esquenazi。详情请参见 [MIT-LICENSE](https://github.com/nesquena/rabl/blob/master/MIT-LICENSE)。