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 把會話數據存入應用的數據庫
session_store = :active_record_store