路由(Routing)

    不同于静态网页的路由是直接对应于档案的目录结构,一个Web开发框架会将路由功能纳入其中,来获得最大的弹性。也就是您可以指定任意URL对应到任一个ControllerAction。另一方面,我们也不在Views中直接写死URL网址,而是透过Helper辅助方法根据你的路由设定来产生URL,这样也可以确定该网址一定有对应的Controller和Action,不然就会出现NoMethodError找不到Helper方法的错误。

    也就是,路由系统做几件事情:

    1. 辨识HTTP RequestURL网址,然后对应到设定的Controller Action

    2. 处理网址内的参数字串,例如:/users/show/123送到Users controllershow action时,会将 设定为 123

    3. 辨识link_toredirect_to的参数产生URL字串,例如

    会产生

    1. <a href="/welcome/say">hola!</a>

    Rails这么弹性的路由功能,可以怎么用呢?例如设计一个部落格网站,如果是没有使用框架的CGIPHP网页开发,会长得这样:

    1. http://example.org/?p=123

    但是如果我们想要将编号放在网址列中呢?

    1. http://example.org/posts/123

    或是希望根据日期:

    1. http://example.org/posts/2011/04/21/

    或者是根据不同作者加上文章的标籤(将关键字放在网址中有助于SEO):

    1. http://example.org/ihower/posts/123-ruby-on-rails

    这些在Rails只需要修改config/routes.rb这一个路由档案,就可以完全自由自定。让我们看看有哪些设定方式吧:

    1. get 'meetings/:id', :to => 'events#show'

    这里的events#show表示指向events controllershow action。通常会简写成:

    1. get 'meetings/:id' => 'events#show'

    其中有冒号:id的部分,会被转成一个参数params[:id]传进Controller里。

    外卡路由

    1. match ':controller(/:action(/:id(.:format)))', :via => :all

    这是我们在上一章所使用的方式,也是Rails 3.0之前版本的默认方式。其中的括号用法表示这部份可有可无,也就是上述这一行设定就包括六种路径方式:

    1. match '/:controller', via: :all
    2. match '/:controller/:action', via: :all
    3. match '/:controller/:action/:id', via: :all
    4. match '/:controller.:format', via: :all
    5. match '/:controller/:action.:format', via: :all
    6. match '/:controller/:action/:id.:format', via: :all

    例如,像这样的网址便会对应到welcome controllersay action。外卡路由是一种非常简便的对应方式。这种方式的缺点当网站的Action变多的时候,会容易让Controller的设计变得混乱没有规则。稍后介绍的RESTful路由则是Rails对此提出的组织路由方案。

    还有,(.format)这一段则会让路由可以接受.json.xml等有副档名的网址,并且转成params[:format]参数传进Controller里,搭配respond_to而回传不同的格式。

    命名路由Named Routes

    Named Routes可以帮助我们产生URL helpermeetings_urlmeetings_path,而不需要用{:controller => 'meetings', :action => 'index'}的方式:

    Redirect

    在路由中可以直接设定转向:

    1. get "/foo" => redirect("/bar")
    2. get "/ihower" => redirect("https://ihower.tw")

    要设定网站的首页,请设定:

    1. root :to => 'welcome#show'

    HTTP动词(Verb)限定

    可以透过 :via 参数指定 HTTP Verb 动词

    1. match "account/overview" => "account#overview", :via => :get
    2. match "account/setup" => "account#setup", :via => [:get, :post]

    或是

    1. get "account/overview" => "account#overview"
    2. get "account/setup" => "account#setup"
    3. post "account/setup" => "account#setup"

    Scope 规则

    scope方法可以让我们DRY我们的路由规则,将共通的controllerconstraints、网址前置pathURL Helper前置名称移到scope成为参数。例如

    1. get 'foo/meetings/:id', :to => 'events#show'
    2. post 'foo/meetings', :to => 'events#create'

    可以改写成

    1. scope :controller => "events", :path => "/foo", :as => "bar" do
    2. get 'meetings/:id' => :show, :as => "meeting"
    3. post 'meetings' => :create , :as => "meetings"
    4. end

    其中as会产生URL helperbar_meeting_urlbar_meetings_url

    Module参数则可以让ControllerModule,例如

    1. scope :path => '/api/v1/', :module => "api_v1", :as => 'v1' do
    2. resources :projects
    3. end

    如此controller会是ApiV1::ProjectsController,网址如/api/v1/projects,而URL Helperv1_projects_path这样的形式。

    领域名称Namespace

    NamespaceScope的一种特定应用,特别适合例如后台接口,这样就整组controller、网址pathURL Helper前置名称`都影响到:

    1. namespace :admin do
    2. resources :projects
    3. end

    如此controller会是Admin::ProjectsController,网址如/admin/projects,而URL Helperadmin_projects_path这样的形式。

    Namespace下也可以设定它的首页,例如:

    1. namespace :admin do
    2. root "projects#index"
    3. end

    就样连http://localhost:3000/admin/就会使用ProjectsController index action了。

    特殊条件限定

    我们可以利用:constraints设定一些参数限制,例如限制:id必须是整数。

    另外也可以限定subdomain子网域:

    1. namespace :admin do
    2. constraints subdomain: 'admin' do
    3. end
    4. end

    甚至可以限定IP位置:

    1. constraints(:ip => /(^127.0.0.1$)|(^192.168.[0-9]{1,3}.[0-9]{1,3}$)/) do
    2. match "/events/show/:id" => "events#show"
    3. end

    我们在第六章介绍过RESTful路由的来龙去脉,接下来仔细看看其中的设定。

    复数资源

    单数资源Singular Resoruce

    1. resource :map

    特别之处在于那就没有index action了,所有的URL Helper也皆为单数形式,显示出来的网址也是单数。

    当一个Resource一定会依存另一个Resource时,我们可以套叠多层的Resources,例如以下是任务一定属于在专案底下:

    1. resources :projects do
    2. resources :tasks
    3. end

    如此产生的URL Helperprojecttasks_path(@project)project_task_path(@project, @task),它的网址会如_projects/123/tasksprojects/123/tasks/123

    指定Controller

    resource默认采用同名的controller,我们可以改指定,例如

    1. resources :projects do
    2. resources :tasks, :controller => "project_tasks"
    3. end

    自定群集路由Collection

    除了惯例中的七个Actions外,如果你需要自定群集的Action,可以这样设定:

    1. resources :products do
    2. collection do
    3. get :sold
    4. post :on_offer
    5. end
    6. # 或
    7. get :sold, :on => :collection
    8. post :on_offer, :on => :collection
    9. end

    如此便会有soldproducts_pathon_offer_products_path这两个_URL Helper,产生出如products/soldproducts/on_offer这样的网址。

    自定特定元素路由Member

    如果需要自定对特定元素的Action

    1. resources :products do
    2. member do
    3. get :sold
    4. end
    5. # 或
    6. get :sold, :on => :member
    7. end

    如此会有soldproduct_path(@product)这个_URL Helper,产生出如products/123/sold这样的网址。

    透过exceptonly参数,我们不一定要启用默认的七个Resource路由,例如

    1. resources :events, :except => [:index, :show]
    2. resources :events, :only => :create

    PATCH v.s. PUT

    PATCH是一个相对新的HTTP verbRails为了保持相容性这两个HTTP verbs都会进到update action之中。而编辑表单默认则是用PATCH。在REST语意上的差别是:

    • PATCH 用于修改部分资料

    HTTP API设计有兴趣的读者,可以参考一文。

    rake routes

    如果你不清楚这些路由设定到底最后的规则是什么,你可以执行:

    这样就会产生出所有URL HelperURL 网址和对应的Controller Action都列出来。

    常见错误

    Routing Error

    URL找不到任何路由规则可以符合时,会出现这个错误。例如一个GET的路由,你用buttonto送出_POST,这样就不符合规则。

    ActionController::UrlGenerationError

    当一个路由Helper的参数不够的时候,会出现这个错误。例如eventpath(event)这个方法的_event参数不能是nil。如果你打错成event_path(@events)@events是个nil,就会出现这个错误。

    结论

    透过RESTfulNamed Route,我们就不再需要透过外卡路由的Hash来指定路由了。所有的路由规则都可以在routes.rb一目了然。