承續昨天的實作,今天就來補上 Routing 的最後一個步驟
還記得我們怎麼實作 MiniSinatra 的 DSL
嗎?
# MiniSinatra
def get(path, &block)
MiniSinatra.application.add_route('GET', path, &block)
end
get '/' do
'hello world !'
end
是不是跟我們的 just_do 裡面的 routes.rb 很像?
Mavericks.application.routes.draw do
root to: 'tasks#index'
resources :tasks
end
透過這樣的判斷,我們知道 root
和 resources
其實也就是一個 mehtod,但為什麼這些 method 可以被包在一個 block 裡面去執行呢?這就是我們今天會提到的一個很重要的觀念 - instance_eval
先來看看一個範例
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj.instance_eval do
puts self
puts @v
end
這裡我們建立了一個 MyClass
,在裡面存放了一個實體變數叫 @v
並且給予 1
的值,接著 new 一個 MyClass
的物件以後,執行 instance_eval
,裡面會印出什麼呢?
答案是
#<MyClass:0x00007f85a3033538>
1
從這個例子我們可以知道,在 instance_eval
這個 block 裡面執行的環境會是「這個物件」本身,所以可以透過這樣的方式來取得「這個物件」的內容,當然也可以為「這個物件」定義方法
像是這樣
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj.instance_eval do
def v
@v
end
end
puts obj.v
可以看到我們一開始並沒有定義 attr_accessor
,但之後透過 instance_eval
來替「這個物件」加上,為什麼我一直強調「這個物件」呢,因為用 instance_eval
定義出來的方法,其他物件並不能共用
class MyClass
def initialize
@v = 1
end
end
obj = MyClass.new
obj2 = MyClass.new
obj.instance_eval do
def v
@v
end
end
puts obj.v
# 1
puts obj2.v
# undefined method `v'
所以如果要定義出,不管其他物件也可以使用的方法,就要用另一個很類似的 method,只是作用在 class 上的 class_eval
class MyClass
def initialize
@v = 1
end
end
MyClass.class_eval do
def v
@v
end
end
obj = MyClass.new
obj2 = MyClass.new
puts obj.v
puts obj2.v
至於為什麼 instance method
,反而要用 class_eval
來定義,而不是用 instance_eval
?
那是因為 instance method
實際存放的位置是在 Class
上面,只有定義在 Class
上,才可以讓每一個 new 出來的物件做共享,物件裡面存放的只有實體變數
剛剛我們已經說明了 instance_eval
,現在再來看看 draw
def draw(&block)
mapper = Mapper.new(self)
mapper.instance_eval(&block)
end
就可以知道,我們利用 instance_eval,來去執行裡面的 Mapper
物件裡面的 method,而在 Mapper
,就是我們的各種定義 DSL 語法的地方
# mavericks/lib/action_dispatch/routing/mapper.rb
module ActionDispatch
module Routing
class Mapper
def initialize(route_set)
@route_set = route_set
end
def get(path, to:, as: nil)
# to => "controller#index"
controller, action = to.split("#")
@route_set.add_route("GET", path, controller, action, as)
end
def root(to:)
get "/", to: to, as: 'root'
end
def resources(plural_name)
get "/#{plural_name}", to: "#{plural_name}#index", as: plural_name.to_s
get "/#{plural_name}/new", to: "#{plural_name}#new",
as: "new_" + plural_name.to_s.singularize
get "/#{plural_name}/show", to: "#{plural_name}#show",
as: plural_name.to_s.singularize
end
end
end
end
另外可以看到,我們在初始化時傳了一個 route_set
的 instance,是因為我們需要借助 route_set
的 add_route
method 來將規則加到 @routes,讓之後在比對規則時可以取出
因為篇幅的關係,這裡只有實作 get 的部分,接下來一樣別忘記做 autoload
# mavericks/lib/action_dispatch.rb
module ActionDispatch
module Routing
autoload :RouteSet, "action_dispatch/routing/route_set"
autoload :Mapper, "action_dispatch/routing/mapper"
end
end
並且在 all.rb
裡面把原有的 routing.rb
拿掉,換成新的 action_dispatch
# mavericks/lib/mavericks/all.rb
require 'erubi'
require 'yaml'
require "mavericks"
require "active_support"
require "active_record"
require "action_controller"
require "action_dispatch"
接著回到 just_do,除了新增 routes.rb
以外,其他程式碼不需要更動
# just_do/config/routes.rb
Mavericks.application.routes.draw do
root to: 'tasks#index'
resources :tasks
end
然後打開瀏覽器測試看看,沒問題的話,網站應該還是會正常運作!
Mavericks github: https://github.com/apayu/mavericks