昨天我們提到了 Ruby 的 const_missing
,也知道在 Ruby 的世界裡面,用 class
關鍵字定義的時候會自然存在 常數
,我們也提到 Rails 因為沒辦法預先知道檔名和位置,需要靠著 convention over configuration
的方式,來判斷檔案的位置,並且「猜」出開發者所需要的常數(也就是 Class)是什麼,今天就來將這些技巧結合在一起
再開始之前,首先來談談關於 Rails 的檔案命名規則,在程式碼的世界裡面有許多命名的規則,在 Rails 裡面,一般我們會用 Camel Case(駝峰式大小寫)
的方式,來表示 Class,例如 HomeController
或是 TasksController
,在檔名部分,我們則會用蛇底式命名,例如 home_controller.rb
、tasks_controller.rb
有了這樣的慣例以後,接下來就是要處理轉換的問題,怎麼樣讓我們的程式碼可以看到 class TasksController
,就知道要去尋找 tasks_controller.rb
這個檔案並且載入,要實作這樣的尋找過程,我們會用到比對文字
最常使用的正規表示式 Regular expression
# mavericks/lib/mavericks/support.rb
module Mavericks
def self.to_underscore(string)
string.gsub(/::/, '/')
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
.gsub(/([a-z\d])([A-Z])/,'\1_\2')
.tr("-", "_").downcase
end
end
這裡我們借用了早期 Rails 處理命名轉換的一段程式碼,對於有些人來說 像是我,正規表示式一直都學不好,這裡我推薦一個蠻不錯的學習網站 regex101
現在我們就一段一段來看
先做 ::
處理,換成 /
做為路徑
# 將 "Service::HomeController" 轉換成 "Service/HomeController"
string.gsub(/::/, '/')
再來以大寫做為分割並加上底線
# 將 "Service/HomeController" 轉換成 "Service/Home_Controller"
.gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2')
最後把全部字串換成小寫,並將 -
換成 _
# 將 "Service/Home_Controller" 轉換成 "service/home_controller"
.tr("-", "_").downcase
我們有了轉換的方法,接下來就是要做攔截,還記得 const_missing
怎麼用嗎?如果呼叫一個不存在的常數
時,就會觸發 const_missing
,換句話說,當觸發時我們要嘗試剛剛所寫的 規則
,去試圖找出不存在的 常數
這裡建立另一個新的檔案 dependencies.rb
來處理 const_missing
# mavericks/lib/mavericks/dependencies.rb
class Object
def self.const_missing(const)
require Mavericks.to_underscore(const.to_s)
Object.const_get(const)
end
end
別忘了要在 lib/mavericks.rb
底下把所寫的程式碼都載入,就如同前幾天的所提到,lib/mavericks.rb
是 Mavericks 的入口,要在這裡將其他寫好的檔案都載入
# mavericks/lib/mavericks.rb
require "mavericks/version"
require "mavericks/routing"
require "mavericks/support"
require "mavericks/dependencies"
module Mavericks
.
.
(略)
經過一番修改,現在 Mavericks 已經有 autoloading 了,立刻來測試看看吧!
先把原本為了新增 Controller 檔案而加上的 require 都刪掉
# just_do/config/application.rb
require 'mavericks'
$LOAD_PATH << File.join(File.dirname(__FILE__), "..", "app", "controllers")
# 將 controller 的 require 都刪掉
# require 'books_controller'
# require 'home_controller'
module JustDo
class Application < Mavericks::Application
end
end
然後執行 bundle exec rackup -p 3001
,重整一下畫面 http://127.0.0.1:3001/
,well..網頁看起來都正常,那我們再隨便新增一個 controller 看看
# just_do/app/controllers/orders_controller.rb
class OrdersController < Mavericks::Controller
def index
'This is a Order index page'
end
end
一樣重啟伺服器,然後連到 http://127.0.0.1:3001/orders/index
,就會看到下面畫面
This is a Order index page
酷!我們不用在那麼辛苦一個一個載入 Controller 了
雖然我們已經做出 autoloading,但其實 Rails 處理的狀況還要更多,像是 nested namespaces
的問題,例如 Service::MyController,還有各種複雜的狀況,如果有興趣深入研究的讀者可以參考這篇
另外依照現在 Mavericks 的架構,我們用 const_missing
來捕捉找不到 Class 的狀況,但如果載入的檔案還是沒有那個 Class 呢?我們可以嘗試清空 tasks_controller.rb
這個檔案,然後重新載入一次 http://127.0.0.1:3001/tasks/index
你會發現 server 整個 crash 而關掉
那是因為 Ruby 因為載入了檔案,卻發現還是找不到常數,所以又再 call 一次 const_missing
,進入到無限循環的狀態,最後讓 server 當掉,為了避免這個情況發生,我這裡用很簡單的方式做處理
# mavericks/lib/mavericks/dependencies.rb
class Object
def self.const_missing(const)
return nil if @called_const_missing
@called_const_missing = true
require Mavericks.to_underscore(const.to_s)
klass = Object.const_get(const)
@called_const_missing = false
klass
end
end
用一個變數來紀錄有沒有呼叫過 const_missing
,最後再重新測試一下,一樣的狀況,這次就會導向 404
的畫面
除了利用 Autoloading 來減少我們手動加入檔案,還有沒有其他的方式可以加快開發速度?其實有的,還記得我們這幾天一直重覆這樣的動作嗎?
$ gem build mavericks.gemspec
$ gem install mavericks-0.1.0.gem
還不包括刪掉舊的 mavericks-0.1.0.gem
,然後關閉伺服器,再重新打開,重整瀏覽器畫面...其實我們可以利用 Bundler
內建的功能機制,來改善我們的開發流程,先去 Gemfile 修改一下檔案
# just_do/Gemfile
source 'https://rubygems.org'
# 加上 path
gem 'mavericks', :path => "../mavericks"
接下來利 用bundle exec
來執行 Gemfile 裡面的 gem,透過這樣的方式,我們就不用每次 build 完在 install 到本機上,但別忘了我們要把之前 Mavericks 打包的 mavericks-0.1.0.gem
給刪掉,也要把安裝在本機的 mavericks-0.1.0
也一併 uninstall
$ gem uninstall mavericks
接著之後每次只要修改 Mavericks,只要像下面這樣開啟 Rack
伺服器,就可以直接套用最新版的 Mavericks
$ bundle exec rackup -p 3001
是不是很方便呢?
雖然我們已經有了 autoloading,也學到 path
的技巧,但現在每次修改 controller 內容,還是要重新啟動伺服器,這樣開發速度還是很慢,為了更像 Rails 一點,我們可以使用 rerun
這個 gem,來幫助我們改善這個問題
# just_do/Gemfile
source 'https://rubygems.org'
gem 'mavericks', :path => "../mavericks"
group :development do
gem 'rerun'
end
接著在 just_do 目錄底下重新 bundle install
,然後利用 rerun 啟動 server
$ bundle install
$ bundle exec rerun -- rackup -p 3001
開啟後會發現 rerun
正在監聽你的檔案,接著隨意更改一個 Controller,然後直接重新整理,就會發現修改結果立刻顯示,再也不用重新開啟 server 了!
不但是我們已經過了一個禮拜了,我說那個 View 呢?應該不會有人想一直回傳字串吧,尤其現在是大前端的時代,不能寫些 HTML 和 CSS 的網站能用嗎?
嗯...好的,明天我們就要進入 MVC 架構裡面的 V 部分,加上 View 以後,網站看起來也會更加豐富