如果讀者有跟著我一起做到今天,會發現前面幾天在使用 Mavericks 時,每次寫完 code,都需要手動 require 檔案,再重啟伺服器,讓 server 載入新的 code,接著重新整理畫面才會出現新的結果,開發時間一拉長,就會覺得很阿雜...
但如果你開發過一陣子的 Rails,一定會對 Rails 修改程式碼,只需要回到網頁按下重新整理,就會出現改變的結果,感到印象深刻,為什麼 Rails 可以做到?在實作這些東西之前,我們先來建立一些觀念
在實作 Rails 的載入機制之前,我們要先花點時間來了解 require,相信大家都不陌生在 Ruby 裡面要使用其他套件,可以這樣子寫
require 'mavericks'
但你曾經好奇過, Ruby 怎麼知道要去哪裡找嗎?其實都是基於 $LOAD_PATH
這個 global variable ,裡面存放了許多路徑,有興趣的人可以開啟 irb
來查看$LOAD_PATH
2.6.6 :001 > $LOAD_PATH
=> ["/.rvm/rubies/ruby-2.6.6/lib/ruby/gems/2.6.0/gems/did_you_mean-1.3.0/lib"..."]
這也是為什麼在 Ruby 裡面可以直接 require 那些套件的原因,但是光只有 $LOAD_PATH
是不夠的,Ruby 還有另一個 global variable $LOADED_FEATURES
,來存放已經 require 的檔案路徑,為了要驗證 Ruby require 行為,我們先建立一個 hola/
的資料夾,裡面放一個空的檔案叫hello.rb
,接著在同個目錄下開啟 irb
,執行以下程式碼觀察輸出結果
# 先印出 $LOADED_FEATURES
2.6.6 :001 > $LOADED_FEATURES.grep /hello.rb/
=> []
2.6.6 :002 > require './hello.rb'
=> true
# 再檢查一次 $LOADED_FEATURES
2.6.6 :003 > $LOADED_FEATURES.grep /hello.rb/
=> ["/Users/apa/yuapa/it_30_day/demo/hola/hello.rb"]
# 再 require 一次就會出現 false
2.6.6 :004 > require './hello.rb'
=> false
第一次檢查 $LOADED_FEATURES
結果會發現是空值,也因為是空值,路徑還沒被加到 $LOADED_FEATURES
,所以 require 後 return 的結果為 true
,接著再檢查一次 $LOADED_FEATURES 會發現這時候已經包含了 hello.rb
,執行第二次 require 會發現結果為 false
現在我們已經了解 Ruby 怎麼將檔案載入,但載入後,有這麼多的 module 和 class,那尋找的機制是什麼?這裡列出三個規則
其中我們來看第一條規則
# hola/hello.rb
C = "At the top level"
module A
C = "In A"
end
module A
module B
puts Module.nesting # => [A::B, A]
puts C # => "In A"
end
end
module A::B
puts Module.nesting # => [A::B]
puts C # => "At the top level"
end
透過上面的範例我們可以知道,Ruby 會參考 Module.nesting
來尋找常數,因為第一個例子 Module.nesting
有包含 module A
,所以就 Ruby 就會尋找到 A::C
,第二個因為沒有包含,所以就往上找到 ::C
除了剛剛介紹的那些基本觀念以外,Ruby 其實還有內建 autoload 的功能
module A
# 只有建立 constant B
autoload(:B, './b.rb')
# 執行當下才會載入檔案
B
end
只要給予路徑和常數名稱,就可以執行,但有趣的是,在還沒真正執行之前,Ruby 只有先建立常數,並沒有真正把檔案載入,直到執行的當下,才去把檔案載入進來
但是對於 Rails 來說,他不能直接使用 Ruby 的 autoload,因為這樣的方式需要先知道檔案的路徑和名稱,所以 Rails 另外建立了一套自己的機制,還記得在學習 Rails 常提到的 convention over configuration
(慣例優於設定)嗎?在開發 Rails 專案時,我們會依照慣例將 Controller 放在 app/controllers
,Model 放在 app/model
,靠著這樣約定好的機制,搭配另一個大家可能比較少用到的 const_missing
,來達到 autoload
的效果
const_missing
跟另外一個 method_missing
很像,都是 Ruby 找不到東西時觸發的方法,只是一個針對 常數
,一個針對 mehtod
,利用前面建立的 hello.rb
,來做一個範例觀察看看,
# hola/hello.rb
module Hola
def self.const_missing(name)
puts "In #{self} looking for #{name}..."
end
end
接著打開 irb
,
# require hello.rb
2.6.6 :001 > require './hello.rb'
=> true
2.6.6 :002 > Hola::Amo
In Hola looking for Amo...
=> nil
當你在 Ruby 裡面使用了一個常數,Ruby 會先依照前面提到的規則去尋找,如果沒有找到,就會呼叫 Module#const_missing
,以上面的例子來說就是 Hola.const_missing("Amo")
,而 Rails 就是覆寫了這個 method,在沒有找到常數時,利用我們剛剛提到的 convention over configuration
,去找相對應的檔案載入進來,來「猜」開發者想要的常數是什麼
跟 Ruby 的 預先知道檔案名稱
相反的是,Rails 是我不需要現在知道檔案名稱,但你需要按照約定的方法放檔案,等要用到的時候,我在去載入
,而 Rails 預設是載入 app/
底下的檔案,也可以透過 autoload_paths
來做修改
現在我們知道 Ruby 的 request 的基本原理,也大概了解 Ruby 尋找常數的規則,也練習使用 const_missing
, Rails 在這些基礎原理上,做了其他更多的事情,這也是 Rails 時常被稱呼為黑魔法的原因之一,而明天我們將要來實作剛剛所提到的,怎麼樣用 convention over configuration
搭配 const_missing
,來實現 autoload
參考:
Rails autoloading — how it works, and when it doesn't
Everything you ever wanted to know about constant lookup in Ruby