Routes的几种用法整理

resources

最常见的 Routes 用法是 resources ,比如说

resources :users

Rails 就会默认替我们生成对应 7 个 action 的路径,并且生成相应的 helper 方便我们在 view 中使用

controller#actionHTTP verbpathhelper
users#indexGET/usersusers_path
users#createPOST/usersusers_path
users#newGET/users/newnew_user_path
user#editGET/users/:idedit_user_path(user_id)
user#showGET/users/:iduser_path(user_id)
user#updatePUT/PATCH/users/:iduser_path(user_id)
user#destroyDELETE/users/:iduser_path(user_id)

也就是我们常说的 CRUD 的 7 个 actions。

生成的 helper 除了 _path 后缀之外,其实还有一个 _url 后缀,前者是指向相对路径,后者是指向绝对路径。

如果用单数的 resouce ,会产生 6 个 action,没有了 index

resource :user
controller#actionHTTP verbpathhelper
users#createPOST/userusers_path
users#newGET/user/newnew_user_path
user#editGET/user/editedit_user_path
user#showGET/useruser_path
user#updatePUT/PATCH/useruser_path
users#destroyDELETE/useruser_path

在 Rails 中,Routes 的作用是,建立 View 和 Controller 之间的连接通道。通过 Routes 建立的每一个路径,都应该指向某个 controller 里的某一个 action。Rails 遵循的一个原则是 “Convension Over Configuration”,换句话说,在 Rails 里有很多「约定俗成」的规则。一句 resources 就生成 7 个 action ,就是一种约定俗成的做法。

通过 resourcesresource 生成路径的对比,可以看出一个规则

复数形式:指向某个对象时,需要输入 id

单数形式:全部都是指向单个对象,不需要输入 id

除了 convenstion ,Rails 其实也支持 configuration ,也就是说也可以自定义。

单条资源请求

比如说,指向一个单数资源,有时候也并不需要传递 id,比如说网站用户的信息编辑页面,也就是 users#show 。如果用 resources 来生成的话,路径就是会是 /users/:id

因为只有登录的用户才可以进入自身的信息编辑页面,既然已经登录,用户的 id 已经通过 session 获取了,就并不需要在网址上传递自身的 id。在 routes 里就可以这样来设定

get 'profile', to: 'users#show'

或者

get 'profile' => 'users#show'

这样得到的网址是 /profile,HTTP verb 是 GET ,指向是 users 这个 controller 下 show 这个 action, helper 是 profile_pathprofile_url

namespace

最常用的场景就是,针对不同的用户,我们对同一个数据库有不同的操作。

比如说 users 这个 model,对于普通用户,应该可以对于自身的信息进行编辑,需要 controller 来实现;对于管理员,应该可以对所有用户的某些信息进行编辑,比如说修改用户的权限等,也需要 controller 来实现。显然,普通用户和管理员最好能用不同的 controller,比如说 admin/users_controlleraccount/users_countroller

这种情况下,在 routes 里也应该有相应的指向,就会用到 namespace。

namespace :admin do
  resources :users
end

这样得到的 path 都带一个 /admin/.. ,controller 也是对应到 /admin 下的 controller,得到的 helper 也会带一个 admin_users 前缀。比如说

controller#actionHTTP verbpathhelper
admin/users#indexGET/admin/usersadmin_user_path

Scope

这是一个特殊的使用场景,假如我们不希望在网址中显示 /admin 这个前缀,但又想把路径指向 /admin 下的 controller,这时就可以用 scope

# 批量定义
scope module: 'admin' do
  resources :users
end

# 单个定义
reources :users, module: 'admin'

这样得到的结果是(以 Index 为例)

controller#actionHTTP verbpathhelper
admin/users#indexGET/usersusers_path

注意到,与 namespace 不同的是 path 中没有显示 /admin 这个前缀,helper 中也没有 admin_ 的前缀,但实际上都是指向了 admin/users_controller

反过来,如果我们想在 url 中显示 /admin ,但不想把 controller 放在 /admin 下,也可以用 scope 来实现

scope '/admin' do
  resources :users
end

这样的效果是

controller#actionHTTP verbpathhelper
users#indexGETadmin/usersusers_path

nested resources

嵌入路由。

resources :users do
  resources :posts
end

这里的 userpost 之间的关系一般是::user has_many :posts。也是说,一个用户可以发表很多篇文章,当要对某一用户所发表的文章进行操作时,那就可能要通过 url 请求传递两个信息,一个是用户的 id,另一个可能是文章的 id。通过嵌入式的路由,Rails 会帮我们生成这样的 url,例如上述的例子,得到的结果是

controller#actionHTTP verbpathhelper
posts#indexGET/users/:user_id/postsusers_posts_path
posts#createPOST/users/:user_id/postsusers_posts_path
posts#newGET/users/new/:user_id/posts/newnew_user_post_path(user_id, id)
posts#editGET/users/:id/:user_id/posts/:id/editedit_user_post_path(user_id, id)
posts#showGET/users/:id/:user_id/posts/:iduser_post_path(user_id, id)
posts#updatePUT/PATCH/users/:id/:user_id/posts/:iduser_post_path(user_id, id)
posts#destroyDELETE/users/:id/:user_id/posts/:iduser_post_path(user_id, id)

使用嵌入路由的时候要注意,一般只嵌入一次,不要多次嵌入,这样会造成 URL 的请求的信息过多,而实际上我们往往并不需要那么多的信息。

比如说 user 下有 post, post 下有 comment,一般把 post 嵌到 user 下,把 comment 嵌到 post 下,但要分成两个嵌入

resources :users do
  resources :posts
end

resources :posts do
  resources :comments
end

不要写成这样

resources :users do
  resources :posts do
    resources :comments
  end
end

user 和 post 是一对多的关系,post 和 comment 也是一对多的关系。有了 post_id ,就可以回溯得到 user_id,没有必要再 URL 里增加一个 user_id 的请求。

排除不需要的 action 和请求方式

通过 resources 可以得到 7 个 action 的请求方式,但有时候我们并不需要那么多,这时可以用 only: 来指定。

resources :users, only: [:index, :destroy]

自上而下的机制

如果在 routes 中对同一个路由多次定义,Rails 只认第一次定义,比如

get '/users' => 'users#index'
get '/users' => 'users#index_old'

那么 Rails 实际只认第一个路由。

member 和 collection

当我们需要自定义 RESTful 的路由的时候,可以用 membercollection。当需要在 URL 里传递 :id 的时候用 member ,不需要传递 :id 的时候用 collection

# 批量指定
resources :users do
  member do
    post :status
    #...
  end
  
  collection do
    get :online
    #...
  end
end

# 单个指定
resources :users do
  post :status, on: :member
  get :online, on: :collection
end

得到的结果会是

controller#actionHTTP verbpathhelper
users#statusPOST/users/:id/statusstatus_user_path
users#onlineGET/users/onlineonline_users_path

也可以简单地理解,单数时用 member,复数时用 collection

重定向

在 routes 里是可以做重定向的,例如

get '/articles', to: redirect('/posts')

as 来定义 helper

例子:

get '/welcome/hello' => 'welcome#hello', as: 'welcome_hello'

这里自定义了一个路由,:as 的作用是生成一个 welcome_hello 的 helper,这样我们在 view 里可以用 welcome_hello_path 或者 welcome_hello_url 来指向这个地址了。

在用 member 或者 collection 自定义 RESTful 路由的时候,我们也可以自定义 helper

resources :users do
  post :status, on: :member, as: 'profile' 
end

这样的得到的 helper 就不是 status_user ,而是 profile_user 了。

对同一个 controller 做批量指定

例子:

controller ':welcome' do
  get '/welcome/hello'
  get '/welcome/plan'
end

得到的结果是

controller#actionHTTP verbpathhelper
welcome#helloGET/welcome/hellowelcome_hello_path
welcome#planGET/welcome/planwelcom_plan_path

关于 link_to

在 view 中,用 link_to 方法可以让 Rails 帮我们生成一个链接,但是对于同一个链接地址,可能会对应不同的 action,还需要用请求方式来进一步确定。

比如我们用 resources 定义了:posts ,如果我们在 view 中这么写

<% link_to "update", post_path(post) %>

指向的会是 show 这个 action,而不是 update

因为 link_to 方法如果没有指定请求方式的话,默认会用 GET 来请求。 post_path(post) + GET 的请求对应的是 posts#show。如果想要指向 posts#update ,需要这么来指定

<% link_to "update", post_path(post), method: :patch %>
·