天天看點

rails on ruby,ruby on rails 之Action Dispatch

Action Pack 是 Rails 應用的核心,包含三個 Ruby 子產品:ActionDispatch、ActionController 和 ActionView。 Action Dispatch 負責把請求分派給控制器,就是我我們平時所說的路由; Action Controller 處理請求,得到響應; Action View 供 Action Controller 使用,用于格式化響應。以前一直覺得Dispatch這個名詞很是高大上,但是讀過《rails 靈活開發》之後,突然覺得這部分也不是太晦澀。

Rails 對這種接口提供了直接支援,它提供了一個路由宏方法,即 resources。 下面是我們在一個命名空間裡面定義了兩個路由:

namespace :backend do
  root 'home#index'
  resources :admins, except: %i[show]
end
           

我們可以利用 rake routes 指令查詢resource生成的路由:

backend_root       GET      /backend(.:format)                  backend/home#index
backend_admins     GET      /backend/admins(.:format)           backend/admins#index
new_backend_admin  GET      /backend/admins/new(.:format)       backend/admins#new
                   POST     /backend/admins(.:format)           backend/admins#create
edit_backend_admin GET      /backend/admins/:id/edit(.:format)  backend/admins#edit
backend_admin      PATCH    /backend/admins/:id(.:format)       backend/admins#update
                   PUT      /backend/admins/:id(.:format)       backend/admins#update
                   DELETE   /backend/admins/:id(.:format)       backend/admins#destroy
           

我們也順便來看看我們控制器是什麼樣子的:

def index
   @roles = Role.where(manager_id: current_admin.id)
end

def new
    @admin = Admin.new
end

def create
     @admin = Admin.new(admin_params)
     if @admin.save
       redirect_to backend_admins_url, notice: "Admin was successfully created(register_admin)"
     else
       render :new
     end
end

def edit
    end

def update
    if @role.update(role_params)
      redirect_to backend_admins_url, notice: "Admin was successfully updated."
    else
        render :edit
     end
end
           

我們首先要來看看301和302重定向狀态碼的差别。301,302 都是HTTP狀态的編碼,都代表着某個URL發生了轉移,不同之處在于:301 redirect: 301 代表永久性轉移(Permanently Moved),302 redirect: 302 代表暫時性轉移(Temporarily Moved )。但其實對使用者效果是一樣,代表某個網頁對跳轉。

我們來分析一下resource的路由:

  • backend_root,這叫路由的輔助方法,便于我們直接在控制器和視圖中直接調用。對應的url為http://localhost:3000/backend,這是http方法為get。我們可以直接解析,也是非常容易了解的
  • backend_admins,這個路由輔助方法對應的是控制器的index方法。url為http://localhost:3000/backend/admins,http方法為get,了解并不難
  • new_backend_admin, 路由輔助方法對應的是控制器的index方法, url為http://localhost:3000/backend/admins/new, http方法為get
  • 然後是一個沒有路由輔助方法的路由,那就是通過表單建立一個Admin的執行個體,并儲存。 首先通路的url是http://localhost:3000/backend/admins,http方法是post,這時候伺服器傳回給浏覽器一個302重定向的狀态碼(利用redirect to方法),讓浏覽器重定向到http://localhost:3000/backend/admins/new。如果執行個體沒有儲存成功,我們可以利用render方法,渲染同一個控制器裡面的模闆new
  • edit_backend_admin,url為http://localhost:3000/backend/admins/2/edit,http方法為get
  • backend_admin,url為http://localhost:3000/backend/admins/2, http方法為patch

resources :products do

get :who_bought, on: :member

end

把請求分派給控制器

簡單來說,Web 應用接收浏覽器發來的入站請求,處理之後再發出響應。實際上,Rails 提供了兩種分派請求的方式:一種是在需要時可以使用的詳盡方式,另一種是通常使用的便利 方式。

處理請求

前面我們讨論來如何把Action Dispatch 如何把入站請求分派給應用中适當的代碼,這一節讨論代碼内部的事情。

控制器處理請求時,(1)會尋找與入站動作同名的公開執行個體方法,如果找到就會調用那個方法。如果未找到,而控制器實作了 method_missing() 方法,就會調用這個方法,并傳入動作名稱作為第一個參數,空的參數清單作為第二個參數。(2)如果未找到可調用的方法,控制器會尋找使用目前控制器和動作命名的模闆,如果找到就會直接渲染那個模闆。如果這些嘗試都失敗了,則會抛出 AbstractController::ActionNotFound 錯誤。

控制器為動作提供環境(進而也為動作調用的視圖提供環境)。下述方法中有很多能直接通路 URL 或請求中:

的資訊。

1. action_name

目前處理的動作的名稱。

2. cookies

和請求相關的 cookie。發送響應後,請求對象中的值存儲在浏覽器的cookie 中。Rails 對會話的支援就基于cookie。

3. headers

一個散列,設定響應使用的 HTTP 首部。預設把 Cache-Control 設為 no-cache。有特殊用途的應用可能需要設定 Content-Type 首部。注意,不要直接在首部中設定 cookie 值,而應該使用 cookie API。

4. params

類似散列的對象,其中包含請求參數(以及路由分派過程中生成的僞參數)。之是以說是類似散列的對象,是因為其中的條目可以通過符号或字元串索引,例如 params[:id] 和params[‘id’] 傳回相同的值。符 合習慣的做法是使用符号形式。

5. request

入站請求對象。具有下述屬性: request_method 傳回請求方法,即 :delete,:get ,:post 或 :put 中的一個。

Rails 會話

首先我想明确rails會話的概念。在這之前,先來回顧一下session和cookie之間的差別:

1:session 在伺服器端,cookie 在用戶端(浏覽器)

2:session 預設被存在在伺服器的一個檔案裡(不是記憶體)

3:session 的運作依賴 session id,而 session id 是存在 cookie 中的,也就是說,如果浏覽器禁用了cookie ,同時session也會失效(但是可以通過其它方式實作,比如在 url 中傳遞 session_id)

4:session 可以放在 檔案、資料庫、或記憶體中都可以。

5:使用者驗證這種場合一般會用 session 是以,維持一個會話的核心就是用戶端的唯一辨別,即 session id

Rails 是一種類似于散列的結構,可以實作跨請求留存。與原始的cookie不同,會話中可以存儲任何對象(隻 要對象可以編組),是以特别适合保持 Web 應用的狀态資訊。比如在當我們編寫購物網站的購物車子產品時,Rails每次處理請求都會儲存購物車,更為重要的是,開始處理入站,開始處理入站請求時,Rails 會恢複那個請求的購物車。有了會話,應用就好像 能記住請求一樣。

這就引出一個有趣的問題:在請求之間,這些資料具體存儲在哪裡呢?一種選擇是讓伺服器把這些資料發給 用戶端,存儲在 cookie 中。這是 Rails 預設采用的方式,雖然對資料的大小有限制,而且占用了帶寬,但是 伺服器不用耗費精力管理和清理。注意,會話内容(預設)是加密的,是以使用者無法檢視也無法篡改。但是我現在公司的項目中,并不是這麼做的,而是使用下面的一種方法。

另一種選擇是把資料存儲在伺服器中,這種方式要做很多工作,往往得不償失。首先,Rails 要跟蹤會話。為此,要建立一個(預設)由 32 個十六進制字元構成的鍵(是以有 16^32 種組合方式),這個鍵稱作會話 ID, 實際上是随機的。Rails 會把這個會話 ID 存儲在浏覽器的 cookie 中(鍵為 _session_id),浏覽器後續發送的請求帶有 cookie,是以 Rails 能再次獲得會話 ID。

其次,Rails 要在伺服器中持久存儲會話資料,并使用會話 ID 索引。收到請求後,Rails 使用會話 ID 在存儲 器中查找資料,找到的資料是序列化後的 Ruby 對象。反序列化後,Rails 把結果存入控制器的 session 屬性 中,提供給應用代碼使用,應用可根據需要添加或修改這些資料。處理完請求後,Rails 會把會話資料再次寫入資料存儲器,然後坐等浏覽器發送的下一次請求。關于添加或者修改session的屬性,我們正在做一個登入跳轉的實驗,後面會将例子列出來。

會話中應該存儲些什麼呢?可以存儲任何你想存儲的資料,不過有些限制和注意事項(後面會再補充):

1.一般來說,會話中的對象必須能序列化(使用 Ruby 的 Marshal 子產品中的函數),是以不能存儲 I/O 對象。

2.别在會話資料中存儲大型對象——應該存入資料庫,然後在會話中引用。對基于cookie的會話來說這一點尤其重要,因為 cookie 的大小限制為 4 KB。

在這裡,我們列出一些會話存儲器,當然現在還有很多會話存儲器,但是目前隻是列出兩個:

1.Rails 2.0 起使用的預設會話存儲機制。這種存儲器存儲的是編組後的對象,是以會話中能存儲任何可序列 化的資料,但是總量不能超過 4 KB。

session_store = :cookie_store
比如現在寫在config/initializers/sessions_store.rb檔案中:
Rails.application.config.session_store :cookie_store, key: "#{Rails.env}_apple-service_session"
           

2.使用 activerecord-session_store gem 2 提供的 ActiveRecordStore 把會話資料存入應用的資料庫