说明:文章所有内容均截选自实验楼教程【Rails基础入门】,感兴趣的点击教程即可查看完整内容~
控制器
Action Controller 是 MVC 中的 C(控制器)。路由决定使用哪个控制器处理请求后,控制器负责解析请求,生成对应的请求。Action Controller 会代为处理大多数底层工作,使用易懂的约定(记住:多约定,少配置),让整个过程清晰明了。
在大多数按照 REST 规范开发的程序中,控制器会接收请求,从模型中获取数据,或把数据写入模型,再通过视图生成 HTML。如果控制器需要做其他操作,也没问题,以上只不过是控制器的主要作用。
因此,控制器可以视作模型和视图(数据库)的中间人,让模型中的数据可以在视图中使用,把数据显示给用户,再把用户提交的数据保存或更新到模型中。
控制器其实也是一个数据枢纽。之所以这么说,是因为通过浏览器提交的数据比如:URL查询字符串,POST的数据等,又如从数据库中查询得到的数据,都会在控制器中做一些处理,然后转发给其他对象。比如,控制器从浏览器提交的数据中,解析出各种参数,然后根据这些参数在数据库中操作对象,最后将操作的结果通过视图展示给用户。
1 控制器组成
在 Rails 中,控制器其实就是一个类,继承自ApplicationController
,在上一节的实验中,我们有看到welcome
控制器,其代码如下:
class WelcomeController < ApplicationController
def index
end
end
可以看到其也继承自ApplicationController
。我们上面上面说到,Rails 应用接到请求后,路由决定执行哪个控制器和控制器的哪个动作。这里的动作,就是控制器的各种方法,比如上面代码中的index
方法。同时在 默认约定
的情况下,当用户访问/welcome/index
url的时候,路由会默认执行 WelcomeController
的index
方法,可以看到这是和URL一一对应的。
现在假如,我们有一个控制器:
class ClientsController < ApplicationController
def new
end
end
如果按照默认路由,当用户访问/clients/new
新建客户,Rails会创建一个ClientsController
实例,运行new
方法。同时我们注意到在上面这段代码中,即使new
方法是空的也没办法,因为默认会渲染new.html.erb
视图,除非指定执行其他操作。
2 控制器中的参数
控制器的动作中,往往需要获取用户发送的数据,或其他参数。在网页程序中参数分为两类。第一类随 URL 发送,即 URL 中 ? 符号后面的部分,一般被称为查询字符串。第二类经常称为POST 数据
,一般来自用户填写的表单。之所以叫做POST 数据
是因为,只能随 HTTP POST 方法发送。Rails 不区分这两种参数,在控制器中都可通过params
Hash 获取,看下面的代码:
class ClientsController < ApplicationController
# 这个动作可以接收查询字符串中的status参数,比如下面的这种请求方式
# /clients?status=activated
def index
# 通过 params 哈希进行获取参数
if params[:status] == "activated"
@clients = Client.activated
else
@clients = Client.inactivated
end
end
# 这个方法可以接收 POST 方法提交的参数
def create
@client = Client.new(params[:client])
if @client.save
redirect_to @client
else
render "new"
end
end
end
在上面的代码中,我们定义了一个ClientsController
控制器,它的index
方法(动作)可以接收形如/clients?status=activated
的参数,第二个方法可以接收通过POST请求提交的参数。可以看到不管是通过查询字符串还是POST方法提交的参数我们都可以通过param
哈希获取。那有什么办法可以判断请求的方法呢?我们在控制器动作中还可以访问request
对象,通过该对象可以获取到请求的方法:通过request.method
可以获取请求的方法,request.get?
可以判断请求的方法是不是GET
。关于request
对象的更多内容可以参考 Rails API 文档: http://api.rubyonrails.org/classes/ActionDispatch/Request.html 。
有时候传递数组给 Rails,有什么办法呢?实际上params
哈希不局限于只能使用一维键值对,其中可以包含数组和嵌套的哈希。如果要想发送数组参数给 Ralis,只需要向下面这样请求就可以了:
GET /clients?ids[]=1&ids[]=2&ids[]=3
这样访问params[:id]
就可以获取到["1", "2", "3"]
数组了。
如果想要发送嵌套的哈希参数,只需要在方括号中指定键名就可以了,比如下面是一个通过POST
方法提交的表单:
如果我们向 Rails 提交上面的表单,params[:client]
的值是
{
"name" => "Acme",
"phone" => "12345",
"address" => {
"postcode" => "12345",
"city" => "Carrot City"
}
}
可以看到params[:client][:address
是一个嵌套的哈希。
在 Rails 还可以访问路由传递的参数,所谓路由传递的参数是 URL 的一部分,或者通过在routes.rb
(该文件是Rails项目的路由配置文件)文件中的路由条目指定的参数。假如在routes.rb
文件中有以下一项条目:
get '/clients/:status' => 'clients#index', foo: 'bar'
在这个例子中,当用户访问/clients/active
的时候,params[:status]
的值就是"active"
。同时params[:foo
的值也被设置为"bar"
。
路由
Rails 路由能识别 URL,将其分发给控制器的动作进行处理,还能生成路径和 URL,无需直接在视图中硬编码字符串。简单来说,路由第一个作用是根据请求的URL执行相应控制器的方法,第二个作用是我们可以根据路由生成的路径,类似于 Flask 框架中的 url_for
产生的效果。
当 Rails 程序收到如下请求时:
GET /patients/17
会查询路由,执行相应控制器的方法。如果在routes.rb
中首个匹配的路由是:
get '/patients/:id', to: 'patients#show'
那么这个请求就交给patients
控制器的show
方法处理,并把{id: '17'}
传入params
中。
上面我们说到路由还可以生成路径和URL。如果把前面的的路由修改成
get '/patients/:id', to 'patients#show', as: 'patient'
在控制器在有如下代码:
@patient = Patient.find(17)
相应的视图中有如下代码:
<%= link_to 'Patient Record', patient_path(@patient) %>
那么路由就会生成路径/patients/17
,也就是patient_path(@patient
的计算结果。
Rails 项目的路由配置文件位于 Rails应用目录/config/routes.rb
,以下内容是一个 Rails 项目的routes.rb
文件:
Rails.application.routes.draw do
get 'welcome/index'
# The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes".
# You can have the root of your site routed with "root"
root 'welcome#index'
# Example of regular route:
# get 'products/:id' => 'catalog#view'
# Example of named route that can be invoked with purchase_url(id: product.id)
# get 'products/:id/purchase' => 'catalog#purchase', as: :purchase
# Example resource route (maps HTTP verbs to controller actions automatically):
resources :photos
# Example resource route with options:
# resources :products do
# member do
# get 'short'
# post 'toggle'
# end
#
# collection do
# get 'sold'
# end
# end
# Example resource route with sub-resources:
# resources :products do
# resources :comments, :sales
# resource :seller
# end
# Example resource route with more complex sub-resources:
# resources :products do
# resources :comments
# resources :sales do
# get 'recent', on: :collection
# end
# end
# Example resource route with concerns:
# concern :toggleable do
# post 'toggle'
# end
# resources :posts, concerns: :toggleable
# resources :photos, concerns: :toggleable
# Example resource route within a namespace:
# namespace :admin do
# # Directs /admin/products/* to Admin::ProductsController
# # (app/controllers/admin/products_controller.rb)
# resources :products
# end
end
这个的注释非常有用,比如路由匹配的优先级,以及其他一些使用方法都在注释中有说明。在接下来讲解路由的内容中,请经常回顾这些代码。
1 资源路由
Rails 同时是一个 REST 框架,所以根据需要操作的资源,我们只需要一行代码就可以产生操作资源的各种路由。比如在routes.rb
文件中有这样一行代码:
resources :photos
那么我们现在就有了操作photos
资源(这里是复数资源,也就是需要通过id访问的资源)的一系列路由,并且这些路由会按照约定映射到控制器的相关动作上。按照约定,当我们以resources :photos
定义路由后,会自动有如下的映射关系:
HTTP 方法 | 路径 | 控制器#动作 | 作用 |
---|---|---|---|
GET | /photos | photos#index | 显示所有图片 |
GET | /photos/new | photos#new | 显示新建图片的表单 |
POST | /photos | photos#create | 新建图片 |
GET | /photos/:id | photos#show | 显示指定的图片 |
GET | /photos/:id/edit | photos#edit | 显示编辑图片的表单 |
PATCH/PUT | /photos/:id | photos#update | 更新指定的图片 |
DELETE | /photos/:id | photos#destroy | 删除指定的图片 |
同时当使用 rsources :photos
定义路由后,我们可以使用一些帮助方法,生成对应的路径:
-
photos_path
方法返回"/photos"
路径 -
new_photo_path
方法返回"/photos/new"
路径 -
edit_photo_path(:id
方法返回"/photos/:id/edit"
路径,比如edit_photo_path(10)
返回"/photos/10/edit"
-
photo_path(:id)
方法返回"/photos/:id"
路径,例如photo_path(10)
返回路径"/photos/10"
以上方法都有对应的 _url
形式,也就是将_path
替换为_url
后形成的方法。比如photos_path
有对应的photos_url
方法,前者生成的是站内的路径,后者是生产的带域名,端口一级路径的URL,也就是我们平时访问的网址。
2 单数资源路由
有的时候我们希望不通过ID就能访问资源,比如/profile
页面显示当前登入用户的个人信息。针对这种需求,可以使用单数资源,把对/profile
页面的访问映射到控制器的show
方法。比如我们可以添加以下路由:
get 'profile', to: 'users#show'
将对/profile
页面的访问映射到users
控制器的show
方法上。我们还可以使用以下方式定义单数资源路由:
resources :geocoder
这样会生成六个路由,全部映射到GeocodersController
控制器:
HTTP 方法 | 路径 | 控制器#动作 | 作用 |
---|---|---|---|
GET | /gecoder/new | geocoders#new | 显示新建geocoder的表单 |
POST | /geocoder | geocoders#create | 新建geocoder |
GET | /geocoder | geocoders#show | 显示唯一的 geocoder 资源 |
GET | /geocoder/edit | geocoders#edit | 显示编辑 geocoder 的表单 |
PATCH/PUT | /geocoder | geocoders#update | 更新唯一的 geocoder 资源 |
DELETE | /geocoder | geocoders#destroy | 删除 geocoder 资源 |
3 带命名空间的控制器的路由
你可能想把一系列控制器放在一个命名空间内,最常见的是把管理相关的控制器放在 Admin::
命名空间内(通过rails generate controller Admin::controllern_name action
方式的创建) 。但是我们怎么访问让URL能匹配到这些控制器呢? 可以在 routes.rb
文件中做如下路由声明:
namespace :admin do
resources :posts, :comments
end
上述代码会为posts
和comments
生成很多路由。对Admin::PostsController
来说,
Rails 会生成:
HTTP 方法 | 路径 | 控制器#动作 | 相应的用于生成路径的方法 |
---|---|---|---|
GET | /admin/posts | admin/posts#index | admin_posts_path |
GET | /admin/posts/new | admin/posts#new | new_admin_post_path |
POST | /admin/posts | admin/posts#create | admin_posts_path |
GET | /admin/posts/:id | admin/posts#show | admin_post_path(:id) |
GET | /admin/posts/:id/edit | admin/posts#edit | edit_admin_post_path(:id) |
PATCH/PUT | /admin/posts/:id | admin/posts#update | admin_post_path(:id) |
DELETE | /admin/posts/:id | admin/posts#destroy | admin_post_path(:id) |
以上方式会将/admin/posts
映射到Admin:PostsController
控制器上,但是有的时候,我们想将没有/admin
前缀的路径也映射到Admin:PostsController
可以这么做:
scope module: 'admin' do
resources :posts
end
这样访问/posts
的时候也会映射到Admin:PostsController
。又有的时候,我们不想要使用命名空间,也想把/admin/posts
映射到普通控制器上,那么可以这么做:
scope '/admin' do
resources :posts
end
这样访问/admin/posts
时就会映射到控制器PostsController
上了。
4 嵌套资源
有的时候有些资源不会单独显示,总是跟随者另外一个资源一起显示的。比如博客文章的评论,评论总是属于某一篇博文。这时候该怎么样使用路由标示这样的层级关系呢?
resources :magazines do
resources :ads
end
这样一来,我们就为创建了嵌套路由。上诉路由声明会生成以下路由:
HTTP 方法 | 路径 | 控制器#动作 | 相应的用于生成路径的方法 |
---|---|---|---|
GET | /magazines/:magazine_id/ads | ads#index | 显示指定杂志的所有广告 |
GET | /magazines/:magazine_id/ads/new | ads#new | 显示新建广告的表单,该告属于指定的杂志 |
POST | /magazines/:magazine_id/ads | ads#create | 创建属于指定杂志的广告 |
GET | /magazines/:magazine_id/ads/:id | ads#show | 显示属于指定杂志的指定广告 |
GET | /magazines/:magazine_id/ads/:id/edit | ads#edit | 显示编辑广告的表单,该广告属于指定的杂志 |
PATCH/PUT | /magazines/:magazine_id/ads/:id | ads#update | 更新属于指定杂志的指定广告 |
DELETE | /magazines/:magazine_id/ads/:id | ads#destroy | 删除属于指定杂志的指定广告 |
上述路由还会生成 magazine_ads_url
和 edit_magazine_ad_path
等路由帮助方法。这些帮助方法的第一个参数是 Magazine
实例,例如magazine_ads_url(@magazine)
。
在以上路由声明中,所有ads
的所有路由都嵌套在magazines
下面,有的时候,我们只需要ads
对应控制器的部分方法嵌套在magazines
资源下面。比如/magazines/:magazine_id/ads
可以用于查看具体杂志的所有广告,但是当我们需要删除一个广告时只需要知道这个广告的id就行,不需要知道具体的杂志,所以也不需要DELETE /magazine/:magazine_id/ads/:id
这样的路由,那该怎么做呢?看下面的路由声明:
resources :posts do
resources :comments, only: [:index, :new, :create]
end
resources :comments, only: [:show, :edit, :update, :destroy]
以上的路由声明中,只把Comments
控制器的index
, new
和create
方法嵌套在Posts
资源下,而其他的方法则不进行嵌套。
关于路由内容就暂时介绍到这里,更多的详细内容可以参考:http://guides.rubyonrails.org/routing.html
最后:
文章所有内容均截选自实验楼教程【Rails基础入门】,该教程总共6节,分别如下:
如果你有一定的 Ruby 基础,想通过练习来学习到 Rails 开发,巩固 Ruby 基础知识,那么这个教程就非常适合你~