Oh!Coder

Coding Life

Jbuilder 简介

| Comments

今天记录的是Jbuilder。Jbuilder为我们定义了一种简单的DSL来声明JSON结构,以此来帮助我们制服庞大的hash结构。特别对生成过程中包含条件和循环的情况有帮助。Rails中默认集成了Jbuilder,所以如果你使用Rails的话,不需要做任何额外的安装配置工作。

基本使用

对于Jbuilder的基本使用,在文档中主要是以简单的例子来说明。所以这里使用了文档中的例子进行说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# app/views/message/show.json.jbuilder

json.content format_content(@message.content)
json.(@message, :created_at, :updated_at)

json.author do
  json.name @message.creator.name.familiar
  json.email_address @message.creator.email_address_with_name
  json.url url_for(@message.creator, format: :json)
end

if current_user.admin?
  json.visitors calculate_visitors(@message)
end

json.comments @message.comments, :content, :created_at

json.attachments @message.attachments do |attachment|
  json.filename attachment.filename
  json.url url_for(attachment)
end

上面这段Jbuilder代码会生成如下的JSON结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "content": "<p>This is <i>serious</i> monkey business</p>",
  "created_at": "2011-10-29T20:45:28-05:00",
  "updated_at": "2011-10-29T20:45:28-05:00",

  "author": {
    "name": "David H.",
    "email_address": "'David Heinemeier Hansson' <david@heinemeierhansson.com>",
    "url": "http://example.com/users/1-david.json"
  },

  "visitors": 15,

  "comments": [
    { "content": "Hello everyone!", "created_at": "2011-10-29T20:45:28-05:00" },
    { "content": "To you my good sir!", "created_at": "2011-10-29T20:47:28-05:00" }
  ],

  "attachments": [
    { "filename": "forecast.xls", "url": "http://example.com/downloads/forecast.xls" },
    { "filename": "presentation.pdf", "url": "http://example.com/downloads/presentation.pdf" }
  ]
}

可以使用set!方法,动态的定义属性和结构的名称:

1
2
3
4
5
json.set! :author do
  json.set! :name, 'David'
end

# => "author": { "name": "David" }

可以直接处理外层数组。对所以和其他集合非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
# @comments = @post.comments

json.array! @comments do |comment|
  next if comment.marked_as_spam_by?(current_user)

  json.body comment.body
  json.author do
    json.first_name comment.author.first_name
    json.last_name comment.author.last_name
  end
end

# => [ { "body": "great post...", "author": { "first_name": "Joe", "last_name": "Bloe" }} ]

还可以直接从数组中取出特定的属性。

1
2
3
4
5
# @people = People.all

json.array! @people, :id, :name

# => [ { "id": 1, "name": "David" }, { "id": 2, "name": "Jamie" } ]

Jbuilder可以直接嵌套在对象内部,作为内部方法对每一个对象进行操作。这对对象的组合非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person
  # ... Class Definition ... #
  def to_builder
    Jbuilder.new do |person|
      person.(self, :name, :age)
    end
  end
end

class Company
  # ... Class Definition ... #
  def to_builder
    Jbuilder.new do |company|
      company.name name
      company.president president.to_builder
    end
  end
end

company = Company.new('Doodle Corp', Person.new('John Stobs', 58))
company.to_builder.target!

# => {"name":"Doodle Corp","president":{"name":"John Stobs","age":58}}

对于Jbuilder既可以单独使用也可以作为ActionView的模版语言进行使用。当需要在Rails中使用的时候,你可以创建类似show.json.jbuilder的文件进行使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
# Any helpers available to views are available to the builder
json.content format_content(@message.content)
json.(@message, :created_at, :updated_at)

json.author do
  json.name @message.creator.name.familiar
  json.email_address @message.creator.email_address_with_name
  json.url url_for(@message.creator, format: :json)
end

if current_user.admin?
  json.visitors calculate_visitors(@message)
end

你还可以使用partial。接下来将会渲染views/comments/_comments.json.jbuilder,为所有这些消息的评论设置一个本地变量comments,并且可以在partial中使用这个变量。

1
json.partial! 'comments/comments', comments: @message.comments

还可以渲染partial的集合:

1
2
3
4
5
6
7
8
9
10
11
12
13
json.array! @posts, partial: 'posts/post', as: :post

# or

json.partial! 'posts/post', collection: @posts, as: :post

# or

json.partial! partial: 'posts/post', collection: @posts, as: :post

# or

json.comments @post.comments, partial: 'comment/comment', as: :comment

你可以往partial模版里传入任何对象,:locals作为传入参数的可选项。

1
2
3
4
5
json.partial! 'sub_template', locals: { user: user }

# or

json.partial! 'sub_template', user: user

如果你愿意,可以特意的让Jbuilder对象返回null

1
2
3
4
5
6
7
8
9
json.extract! @post, :id, :title, :content, :published_at
json.author do
  if @post.anonymous?
    json.null! # or json.nil!
  else
    json.first_name @post.author_first_name
    json.last_name @post.author_last_name
  end
end

支持fragment缓存,借助于Rails.cache工作方式就像HTML模版里的缓存:

1
2
3
json.cache! ['v1', @person], expires_in: 10.minutes do
  json.extract! @person, :name, :age
end

通过使用cache_if!,可以有条件的缓存block:

1
2
3
json.cache_if! !admin?, ['v1', @person], expires_in: 10.minutes do
  json.extract! @person, :name, :age
end

如果你正在为一个对象的集合渲染fragment,那么可以看下jbuilder_cache_multi这个gem。它使用fetch_multi(Rails 4.1及以上版本)一次性获取多个key。

key可以通过key_format!自动格式化,这可以把key名字从标准的ruby格式转换成camelCase格式:

1
2
3
4
json.key_format! camelize: :lower
json.first_name 'David'

# => { "firstName": "David" }

可以通过全局的类方法key_format(例如在environment.rb中做修改):

1
Jbuilder.key_format camelize: :lower

更快的JSON后端

Jbuilder使用了MultiJson,MultiJson默认使用名为JSON的gem。这个gem目前使用ActiveSupport的全ruby#to_json方法实现,但它很慢(Rails 4.1以后纠正了)。为了更快的Jbuilder渲染,你可以指定特定的比如Yajl JSON生成器进行替代。需要在你的Gemfile里包含yajl-ruby这个gem,然后为MultiJson做如下配置:

1
2
require 'multi_json'
MultiJson.use :yajl

更多

嗯,更多可以参看原文档

Comments