How Jbuilder Works

jbuilder是Rails開發(fā)者最常用的gem之一了德撬,自不必多說银酬,它可是 API 開發(fā)中的利器末贾,靈活的DSL語法與Rails和Ruby本身很相配揩晴。本文就要探知一下勋陪,Jbuilder的實現(xiàn)原理是什么,以便我們?nèi)蘸罅蚶迹由钊氲氖褂煤烷_發(fā)Rails API應(yīng)用诅愚。

結(jié)構(gòu)

首先打開Jbuilder的lib目錄,也就是主目錄劫映,我們會看到以下文件結(jié)構(gòu):

├── generators
├── jbuilder
└── jbuilder.rb

我們可以看到违孝,lib下由 一個 jbuilder.rb的入口文件和jbuildergenerators 兩個目錄構(gòu)成的泳赋。其中g(shù)enerators是注冊在Rails中的生成器雌桑,因為本文主要介紹的是jbuilder的工作原理,所以我們就把目光放在 jbuilder目錄中祖今。

├── jbuilder
│   ├── dependency_tracker.rb
│   ├── errors.rb
│   ├── jbuilder.rb
│   ├── jbuilder_template.rb
│   ├── key_formatter.rb
│   └── railtie.rb
└── jbuilder.rb
jbuilder類圖

在Jbuilder 的實現(xiàn)中校坑,我們基本上可以將它的功能部分分為:Jbuilder模塊拣技,template模塊和dependency模塊。

下面我們就來依次介紹它們耍目。

Jbuilder

# lib/jbuilder/jbuiler.rb
Jbuilder = Class.new(begin
  require 'active_support/proxy_object'
  ActiveSupport::ProxyObject
rescue LoadError
  require 'active_support/basic_object'
  ActiveSupport::BasicObject
end)

# lib/jbuilder.rb
require 'jbuilder/jbuilder'
....
class Jbuilder
  @@key_formatter = KeyFormatter.new
  @@ignore_nil    = false
  .....
end

Jbulder類本身是繼承自 ActiveSupport::ProxyBasic類过咬,同時jbuilder使用了打開類的方式,去擴充現(xiàn)有jbuilder類的方法制妄。 繼承ProxyBasic的主要作用就是作為一個潔凈室,讓繼承自它的JbuilderTemplate對象可以通過 method_missnig 去處理非定義方法的調(diào)用泵三。我們這也就道出了Jbuilder及JbuilderTemplate都是使用set! 方法去代理所有未定義的方法耕捞。

  alias_method :method_missing, :set!
  private :method_missing

最后在set!方法會將數(shù)據(jù)保存在 @attributes 屬性中,之后的操作也都是這樣的步驟烫幕,直到在template_bundler中調(diào)用了jbuilder的target方法 將@attributes轉(zhuǎn)換成json數(shù)據(jù)俺抽。

set!方法最后會調(diào)用_write將鍵值對保存到@attributes屬性中,其中Key還會經(jīng)過@key_formatter進行格式化较曼。

# lib/jbuilder.rb
def _write(key, value)
  @attributes = {} if _blank?
  @attributes[_key(key)] = value
end

def _key(key)
  @key_formatter.format(key) # 每次在調(diào)用json.key_format!是都會重新的實例化一個KeyFormatter磷斧。
end

在下面介紹的Template中你就會看到模板處理器最后會調(diào)用 json.target!方法,然后進行渲染捷犹。

# 將Hash 轉(zhuǎn)換為json字符串返回弛饭。
def target!
  ::MultiJson.dump(@attributes)
end

Template

Jbuilder本身就是一個Rails的Railtie,并且它在active_view加載完成后萍歉,注冊了 jbuilder 模板處理器 register_template_handler 侣颂,active_view中規(guī)定如果要注冊 模板的需要一個能夠響應(yīng)call方法的處理類,并且call方法要接受一個template對象枪孩,返回一個字符串對象憔晒,然后action_view會將返回的字符串進行eval運行。

# lib/jbuilder/jbuilder_template.rb
class JbuilderHandler
  cattr_accessor :default_format
  self.default_format = Mime::JSON

  def self.call(template)
    # this juggling is required to keep line numbers right in the error
    %{__already_defined = defined?(json); json||=JbuilderTemplate.new(self); #{template.source}
      json.target! unless (__already_defined && __already_defined != "method")}
  end
end

call方法返回的字符串蔑舞,是用";"分隔的多條語句拒担,模板的代碼也被插入在其中。其中的json是從JbuilderTemplate類中初始化出來的攻询,這樣jbuilder中 json receiver 就是 JbuilderTemplate的實例了从撼。

JbuilderTemplate 也是繼承與Jbuilder類,它在其中擴充了Jbuilder的功能方法有:

  • partial!
  • array!
  • cache!
  • cache_if!

之所以將這幾個方法單獨放在JbuilderTemplate中蜕窿,是因為需要使用ViewContext對象的render方法谋逻,去渲染其他的模板。

#lib/jbuilder/jbuilder_template.rb
  def _render_partial(options)
    options[:locals].merge! json: self 
    @context.render options
  end

在Railtie中 定義模板處理器

# lib/jbuilder/railtie.rb
...
initializer :jbuilder do |app|
  ActiveSupport.on_load :action_view do
    # 向View中注冊處理器
    ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
    # 解決依賴問題
    require 'jbuilder/dependency_tracker'
  end
end

Dependency

jbuilder 注冊template桐经,同時也使用了毁兆,action view的 dependency_tracker 去管理template中對外依賴。

jbuilder/dependency_tracker.rb 類首先繼承自 ::ActionView::DependencyTracker 然后對其核心的dependencies 方法進行了重載阴挣,讓其支持jbuilder自己的規(guī)范方法气堕。

具體的實現(xiàn)就是,使用正則表達式去在template字符串中匹配,jbuilder自己指定的規(guī)則茎芭。

# lib/jbuilder/dependency_tracker.rb      
# Matches:
      #   json.partial! "messages/message"
      #   json.partial!('messages/message')
      #
      DIRECT_RENDERS = /
        \w+\.partial!     # json.partial!
        \(?\s*            # optional parenthesis
        (['"])([^'"]+)\1  # quoted value
      /x

      # Matches:
      #   json.partial! partial: "comments/comment"
      #   json.comments @post.comments, partial: "comments/comment", as: :comment
      #   json.array! @posts, partial: "posts/post", as: :post
      #   = render partial: "account"
      #
      INDIRECT_RENDERS = /
        (?::partial\s*=>|partial:)  # partial: or :partial =>
        \s*                         # optional whitespace
        (['"])([^'"]+)\1            # quoted value
      /x

      def dependencies
        direct_dependencies + indirect_dependencies + explicit_dependencies
      end

      private

      def direct_dependencies
        source.scan(DIRECT_RENDERS).map(&:second)
      end

      def indirect_dependencies
        source.scan(INDIRECT_RENDERS).map(&:second)
      end

我們在Rails View中使用 render template 路徑中不帶擴展名就是因為揖膜,擴展名已經(jīng)注冊到register_tracker方法中了,所以在render 的時候梅桩,action view 會自動的在所以注冊的tracker中尋找匹配的文件壹粟。

其他

KeyFormatter

KeyFormatter非常簡單,就是將傳入的key按照上一次設(shè)置好的格式進行格式化宿百。它的具體實現(xiàn)方法就是趁仙。

在json對象上的key_format! 方法傳入的參數(shù),都會傳入到KeyFormatter的構(gòu)造方法中垦页。

# 傳入Proc對象
json.key_format! ->(key){ "_" + key }
# 或是 Symbol Hash
json.key_format! camelize: :lower
# lib/jbuilder/key_formatter.rb
class KeyFormatter
  def initialize(*args)
    @format = {}
    @cache = {}
    ...
  end
end

在經(jīng)過format方法判斷傳入的是Proc還是Symbol 雀费,Proc的的話就執(zhí)行它,Symbol就使用send方法調(diào)用痊焊,并將參數(shù)傳入盏袄。

并且還會將已經(jīng)格式化過的key緩存下來,避免了相同key多次調(diào)用的開銷薄啥。

def format(key)
  @cache[key] ||= @format.inject(key.to_s) do |result, args|
    func, args = args
    if ::Proc === func
      func.call result, *args
    else
      result.send func, *args
    end
  end
end

Errors

Jbuilder 僅定義了一個異常類辕羽,就是NullError 用于處理為空異常的。

#lib/jbuilder/errors.rb
class NullError < ::NoMethodError
  def self.build(key)
    message = "Failed to add #{key.to_s.inspect} property to null object"
    new(message)
  end
end

總結(jié)

Jbuilder使用上非常簡潔靈活的DSL結(jié)構(gòu)垄惧,其實核心就是通過 method_missing 來將數(shù)據(jù)存放在一個Hash中逛漫,最后再將中其轉(zhuǎn)換成JSON數(shù)據(jù),在配合一下Ruby元編程的技巧赘艳,比如:打開類酌毡,動態(tài)派發(fā)等。其設(shè)計上的方式還是很有借鑒意義的蕾管。不愧是Rails官方出品枷踏。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市掰曾,隨后出現(xiàn)的幾起案子旭蠕,更是在濱河造成了極大的恐慌,老刑警劉巖旷坦,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掏熬,死亡現(xiàn)場離奇詭異,居然都是意外死亡秒梅,警方通過查閱死者的電腦和手機旗芬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捆蜀,“玉大人疮丛,你說我怎么就攤上這事幔嫂。” “怎么了誊薄?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵履恩,是天一觀的道長。 經(jīng)常有香客問我呢蔫,道長切心,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任片吊,我火速辦了婚禮昙衅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘定鸟。我一直安慰自己,他們只是感情好著瓶,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布联予。 她就那樣靜靜地躺著,像睡著了一般材原。 火紅的嫁衣襯著肌膚如雪沸久。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天余蟹,我揣著相機與錄音卷胯,去河邊找鬼。 笑死威酒,一個胖子當(dāng)著我的面吹牛窑睁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播葵孤,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼担钮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尤仍?” 一聲冷哼從身側(cè)響起箫津,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎宰啦,沒想到半個月后苏遥,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡赡模,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年田炭,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片漓柑。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡诫肠,死狀恐怖司澎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情栋豫,我是刑警寧澤挤安,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站丧鸯,受9級特大地震影響蛤铜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜丛肢,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一围肥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蜂怎,春花似錦穆刻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至幽歼,卻和暖如春朵锣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背甸私。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工诚些, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人皇型。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓诬烹,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弃鸦。 傳聞我的和親對象是個殘疾皇子椅您,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)寡键,斷路器掀泳,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,822評論 6 342
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,164評論 25 707
  • 這是一個高速發(fā)展的時代员舵,這是一個信息網(wǎng)絡(luò)化的時代竭业,這是一個物質(zhì)條件不斷豐富的時代陪毡,這是一個需求多元和表達多元的時代...
    博雅大師兄閱讀 379評論 4 4
  • 當(dāng)你感到人生迷茫的時候逐哈,干貨們往往想要從成功人士那里獲取經(jīng)驗吆鹤,或者從書本上找到人生哲理,就像我舆驶,今年21歲了...
    娟兒娟兒閱讀 723評論 2 2