從 Rack 開始的 Rails 探索
IB301 13:00 ~ 14:10 漢語### Rack Rack 在 Ruby 裡面算是一個了一個通用的介面,用來告訴網頁伺服器(Ex. Puma / Passenger)在串接到 Ruby 端的時候,該以怎樣的形式呼叫。也因此,在 Ruby 中基本上只要實作好 Rack 對應的介面,就能夠支援大多數的網頁伺服器。 撇除 Rack 怎們跟網頁伺服器搭配的部分,我們可以很簡單的實作 Rack 來接受一個 HTTP 請求: ```ruby # config.ru app = lambda do |env| # ... [200, {'Content-Type' => 'text/plain'}, ['Hello World']] end run app ``` 有了叫做 `config.ru` 的檔案後,我們就可以用 `rackup` 快速的將一個網頁伺服器跑起來。 ### Dispatch 既然能夠接收 HTTP 請求了,就表示我們得解決第一個問題:如何將網址跟 Controller 關聯起來。 在 Rails 中我們可以很簡單的透過 `config/routes.rb` 這個由 Rails 提供的 DSL 來設定,但是如果要我們自己處理的話,該怎麼做呢? 方法有好幾中,其中一種是繼續使用 Rack 提供的 `Rack::URLMap` 機制(也有其他像是利用 DSL 的方式) ```ruby home = lambda { ... } about = lambda { ... } app = Rack::URLMap.new '/' => home, '/about' => about run app ``` 另外就是像 Rails 或者 Sinatra 一樣,實作個主物件,統一的接收 Rack 傳入的 `env` (環境)然後利用這個資訊來做判斷。 ```ruby class Handler def self.call(env) # ... end end run Handler ``` Rack 之所以會用 `lambda` 來接受 HTTP 請求,是因為規定的統一介面中要有 `call` 方法,也就是說我們只需要定義一個物件帶有 `call` 方法,就能夠正確的接收到 HTTP 請求。 收到 `env` 後,我們就可以利用裡面的 `PATH_INFO` 等透過網頁伺服器解析的環境資訊,來判斷應該要將請求導向哪一個 Controller 來處理。 以 Rails 來說,就會利用 `ActionDispatch` 下相關的機制,將 `/about` 對應到一個我們預期的 Controller 上,這也是為什麼我們大多數時候能夠利用 DSL 就可以快速的將一個 Ruby 檔案跟網址搭配起來的關係。 ### ORM 可以將路徑跟 Controller 搭配起來後,我們就需要處理一些邏輯的部分。大多數時候我們會把實作在 Model 中,而 Model 又常常是跟資料相關的。所以我們需要借助 ORM 來幫助我們處理對資料庫操作的行為。以 Rails 來說,就是利用 ActiveRecord 來處理。 在 Rails 中,會先利用 Arel 來產生 SQL 語法,然後再被 ActiveRecord 封裝成我們常見的 Model 原型,而 Model 相關的行為則是在 ActiveModel 裡面實作的,所以當我們不需要資料庫的時候,也可以利用 ActiveModel 去產生類似 Model 的物件。 ```ruby module API class ExternalUser include ActiveModel::Model end end ``` 這其實也是我們經常誤會的問題,就是 Model 等同於處理資料庫的物件,實際上只是在 Rails 中是以資料庫處理為主,才會直接的從 `ActiveRecord::Base` 繼承 ORM 和 Model 的特性下來。 為了瞭解 Rails 的機制,我們可以用 Sequel 這款 ActiveRecord 的替代品,來實作 Model 和提供 Migration 的功能。 > Demo 示範 ### View View 基本上會選用 Template Engine 來輔助,簡單的做法就是在 Controller 處理完畢後呼叫 Render 方法,來處理。不過如果想要像 Rails 一樣能直接取用當下方法的 Instance Variable 的話,就會需要利用 Ruby 的 Binding 機制來讓我們可以將檔下的 Context 帶入到用來 Render 的 Template Engine 中。 ### Autoload 有了 Router / Controller / Modle / View 這幾項東西後,基本上我們的開發框架就有了一個相對完善的雛形,但是在開發上還是會有非常不方便的地方。那就是每次都需要重開伺服器,因為 Ruby 在開發時是將程式碼讀取到記憶體執行的,即使修改了檔案也不會重新處理。 在 Rails 中,是利用 `const_set` 去將已經讀取過的 Module 和 Class 移除,然後再透過原本 Autoload 的規則找出原始或者可能的檔案位置,重新用 `require` 讀取回來,如此一來就會重新的定義這些 Module 或 Class。 在這邊我們會用 Rails 6 將採用的 Zeitwerk 來實作 Autoload 的機制。 ### 總結 了解一個框架的運作雖然無法馬上做出能替代原本行為的工具,但是這可以幫助我們在使用 Rails 或者除錯上,能夠更輕鬆、清楚的去找到問題的原因或者線索。另一方便,這些方便的特性,其實都在告訴我們 Ruby 這個語言還能夠這樣使用,而不是單純我們所熟悉的那些應用方式,也能讓我們在設計程式讓有更多彈性跟精簡的實作。