[DAY 28] 复刻 Rails - Routing 威力加强版 - 2

承续昨天的实作,今天就来补上 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

透过这样的判断,我们知道 rootresources 其实也就是一个 mehtod,但为什麽这些 method 可以被包在一个 block 里面去执行呢?这就是我们今天会提到的一个很重要的观念 - instance_eval

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 出来的物件做共享,物件里面存放的只有实体变数

mapper

刚刚我们已经说明了 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_setadd_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


<<:  [Day 29] Optimize Images

>>:  [Day 27] 微探讨 Pure pipe 与 Impure pipe

WordPress 如何嵌入响应式 YouTube 影片

想要在 WordPress 上面播放影片,有 2 种方法。 第 1 种是直接上传影片档案,例如上传 ...

[Day10] 学 Reactstrap 就离 React 更近了 ~ Grid 篇‧最终回(应该是?

前言 昨天文章有提到 Grid 其实还有东西没讲完, 所以今日文章就定调为把 Grid 讲完, 然後...

Day 33 打包-ios (部分)

昨天说的是 Android 今天我们来聊聊如何打包 ios, 但因为没有开发帐号,所以就只说 rea...

[Day-29] 小练习-动态进度条

综合过去所学 今天要来练习的是「动态进度条」 废话不多说直接练习吧! 程序码: #include&l...

Day28 Vue CLI建立的专案结构

今天的话来简单介绍一下CLI的各个资料吧。 node_modeules :node 相关的套件 pu...