ActionView 的魔術:ERB & Binding

Rails 作爲一個 MVC 框架,其核心包括三個模塊:ActiveRecord,ActionController 和 ActionView。今天這篇博文的主角是 ActionView,解開模板系統的魔術。

通常情況下,通過 scaffold 已經能夠建立簡單的、包含CRUD基本功能的頁面,完全不需要手動修改 view 的代碼。即使不使用 scaffold ,Rails 也提供了衆多的輔助方法,創造一個功能豐富的動態頁面簡直是易如反掌。但是,會用不代表深入理解,最近有朋友問我這些問題:

  1. 爲什麼編輯一個對象需要在 Controller 創造一個實例變量
  2. View 通過什麼方式訪問這些實例變量的
  3. 那麼多表單輔助方法,都是需要提供 object_name, method 兩個參數,怎麼就變成實例變量的值了

相信大部分的 Rails 程序員手邊的書都是《Agile Web Development with Rails》,書中提到這一點的時候一筆帶過,只是說 Rails 在這裏用了一個小魔術。這裏,我們就來揭開這個魔術吧!

 

Part 1. Template Files - 模板文件

當一個 action 需要返回一段 html 片段的時候,我們需要建立一個模板文件。根據不同的版本、請求類型,模板文件的文件名也各不相同,從早期版本的 action_name.rhtml ,到現在的 action_name.text.html.erb ,以及擴展的 rjs,在擴展名中都包含了一個關鍵字:r / erb。它就是 Rails 模板系統的關鍵:ERb。

 

Part 2. ERb - Ruby Templating

ERb - 嵌入式 Ruby (http://ruby-doc.org/stdlib/libdoc/erb/rdoc/index.html ),是 Ruby 語言提供的一個基本擴展。它支持在字符串中嵌入 ruby 代碼片段。看上去似乎很神祕,其實我們每天都用到,下面這種形式一定不陌生吧?

<p>
  <%= Time.now %>
</p>

沒錯,正是因爲 ERb 的存在,使得模板中可以動態地引用對象的屬性。

 

Part 3. Instance Variables of Ruby - Ruby 的實例變量

讓我們回顧一下 Ruby 語言的基本要素之一:實例變量。通常我們通過

@time = Time.now

的形式創造一個實例變量。這裏我們不重新解釋對於“實例變量”的定義,但是必須牢記一點,正如字面所見,實例變量的作用域是當前實例內,也就是說,只有在實例的內部,纔可以直接對實例變量進行讀寫操作(擴展的訪問子方法等等不在討論範圍之內)。那麼,爲什麼 Rails Controller 裏創建的實例變量能夠在 View 裏面訪問呢,不是自相矛盾嗎?

 

Ruby 作爲一種動態語言,因爲其“開放”的特點,使很多原本不可能的編程模式變爲可能。比如,通過 Open Class 的特性,你可以動態的爲對象注入新的方法定義,或者改寫方法的邏輯,或者,通過不同的方法可以在對象的外部訪問對象的內部的實例變量。經常使用 console 的朋友可能會了解其中的一種方式:Object#instance_variable_get / Object#instance_variable_set 方法。舉個簡單的例子:

class User
  def initialize( name )
    @name = name
  end
end

user = User.new( "Jack" )
user.name #=> raise NoMethodError
user.instance_variable_get('@name') #=> "Jack"
user.instance_variable_set('@name', 'Tom')
user.instance_variable_get('@name') #=> "Tom"

可以看到,在沒有任何訪問子的情況下,我們用這種方式對一個實例變量進行讀寫操作。

 

除了這種簡單的方式,還有另外一種進階的方式,也是 ERb 常用的一種方式:Binding

 

Part 4. Binding

Binding (http://ruby-doc.org/core/classes/Binding.html ) 是 Ruby 語言的自身的一個特性,在任何對象內,self.binding 方法都會返回一個當前對象關聯的 binding 實例。不精確的說,binding 對象可以理解成爲當前對象的完整的上下文環境。文檔中已經包含了一些示例代碼,幫助大家理解 Binding 對象的作用。最重要的一點是:既然是當前對象的完整上下文環境,自然就包括了對象的實例變量。

 

那麼 Binding 在 ERb 中扮演一個什麼樣的角色?是一個運行環境的提供者。

 

回到我們最初的問題,Rails 在 ActionView 中使用了什麼樣的魔法?答案就是 ERb 和 Binding。首先,獲得當前實例的 binding,自然,binding 內也包括了實例變量;緊接着,ERb 允許將模板的內容動態地,綁定到另一個環境中運行。我們依然用剛纔的 User 的例子來說明這一點:

# A simple template string
template = "Hello, <%= @user.name %>"

@user = User.new( "Jack" )

# Get binding
binder = self.send( :binding ) # calling a private method

# Rendering template
puts ERB.new( template ).result( binder )
#=> "Helo, Jack"

雖然例子不是非常恰當,但是足以展示 Binding 和 ERB 的用法。我們可以看到,ERB#result 方法將模板字符串綁定在另外一個環境中運行,而這個環境包含了我們創建的 @user 實例變量,因此,模板中的 @user.name 得到了正確的值。

 

這就是 ERB 的真面目,也是爲什麼在 ActionView 中能夠訪問到 Controller 裏實例變量的原因。感嘆一下動態語言的強大吧!以上只是非常粗糙的講述ERB的使用,Rails 所做的魔法遠不止如此,如果有興趣,可以查看 Rails 源代碼,對於深入學習 Rails 框架也有很大的好處。

 

最後,補充一下 ERB 的應用場景。雖然在普通的需求中,ActionView 所做的已經足夠,但是某些情況還是需要創造獨立的模板系統。比如某個場景,客戶要求提供一個完全自定義的模板系統,這個時候 ERB 就大顯身手了。只需要將數據源載入實例變量中,並且在使用手冊裏列出可以訪問的方法,即使完全不懂 Ruby 語言也可以寫出使用這套簡單的模板系統了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章