Rails 控制器与路由

作者: 蓝桥云课 | 来源:发表于2017-09-21 17:10 被阅读85次

    说明:文章所有内容均截选自实验楼教程【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/indexurl的时候,路由会默认执行 WelcomeControllerindex方法,可以看到这是和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方法提交的表单:

    <form accept-charset="UTF-8" action="/clients" method="post">
      <input type="text" name="client[name]" value="Acme" />
      <input type="text" name="client[phone]" value="12345" />
      <input type="text" name="client[address][postcode]" value="12345" />
      <input type="text" name="client[address][city]" value="Carrot City" />
    </form>
    

    如果我们向 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
    

    上述代码会为postscomments生成很多路由。对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_urledit_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, newcreate方法嵌套在Posts资源下,而其他的方法则不进行嵌套。

    关于路由内容就暂时介绍到这里,更多的详细内容可以参考:http://guides.rubyonrails.org/routing.html

    最后:

    文章所有内容均截选自实验楼教程【Rails基础入门】,该教程总共6节,分别如下:

    image.png

    如果你有一定的 Ruby 基础,想通过练习来学习到 Rails 开发,巩固 Ruby 基础知识,那么这个教程就非常适合你~

    相关文章

      网友评论

        本文标题:Rails 控制器与路由

        本文链接:https://www.haomeiwen.com/subject/fcnzsxtx.html