Rails: 用 Resource_controller 插件给 Controllers 减肥

  • Rick DeNatale
  • 贾晓楠

2008 年 2 月 4 日

话题:RubyRESTRuby on Rails语言 & 开发架构

许多Rails大师提倡的一个导向原则是“丰满的模型,精瘦的控制器”。 Rails 控制器只应该包含一些在模型和视图之间周转用的代码。事务逻辑属于模型,而不属于控制器或视图。在如今支持REST的 Rails 2.0 中,控制器变得非常相象,都包括七个基本的动作(索引、获取、创建、更新、删除、新增和编辑),且具有非常相似的实现方式。在 Rails 2.0 中,可以简单地用 script/generate scaffold standard 来建立控制器框架,即生成了一段具有 REST 特性的控制器代码,如下所示: 

class StandardsController < ApplicationController

  # GET /standards

  # GET /standards.xml

  def index

    @standards = Standard.find(:all)

    respond_to do |format|



      format.html # index.html.erb

      format.xml  { render :xml => @standards }

    end

  end

  # GET /standards/1



  # GET /standards/1.xml

  def show

    @standard = Standard.find(params[:id])

    respond_to do |format|



      format.html # show.html.erb

      format.xml  { render :xml => @standard }

    end

  end

  # GET /standards/new



  # GET /standards/new.xml

  def new

    @standard = Standard.new

    respond_to do |format|



      format.html # new.html.erb

      format.xml  { render :xml => @standard }

    end

  end

  # GET /standards/1/edit



  def edit

    @standard = Standard.find(params[:id])

  end

  # POST /standards



  # POST /standards.xml

  def create

    @standard = Standard.new(params[:standard])

    respond_to do |format|



      if @standard.save

        flash[:notice] = 'Standard was successfully created.'

        format.html { redirect_to(@standard) }

        format.xml  { render :xml => @standard, :status => :created, :location => @standard }

      else

        format.html { render :action => "new" }

        format.xml  { render :xml => @standard.errors, :status => :unprocessable_entity }

      end

    end

  end

  # PUT /standards/1



  # PUT /standards/1.xml

  def update

    @standard = Standard.find(params[:id])

    respond_to do |format|



      if @standard.update_attributes(params[:standard])

        flash[:notice] = 'Standard was successfully updated.'

        format.html { redirect_to(@standard) }

        format.xml  { head :ok }

      else

        format.html { render :action => "edit" }

        format.xml  { render :xml => @standard.errors, :status => :unprocessable_entity }

      end

    end

  end

  # DELETE /standards/1



  # DELETE /standards/1.xml

  def destroy

    @standard = Standard.find(params[:id])

    @standard.destroy

    respond_to do |format|



      format.html { redirect_to(standards_url) }

      format.xml  { head :ok }

    end

  end

end

除了特别的名字以外,所有自动生成的控制器代码都是这样的。

使用自动生成的控制器非常简单。在许多情况下,很少或者不需要对生成的代码做任何改变,尤其是当你把“精瘦的控制器”这个理念铭记于心时。

另一方面,Ruby/Rails 还有一条理念,就是 “不要重复自己(DRY)”。 如果存在几乎重复的代码,即便不是你自己写的,也是违背 DRY 原则的。

输入:resource_controller。James Golick 贡献了一个新的 rails 插件,称为resource_controller,它可以实现与上面同样的控制器,代码如下:

class StandardsController < ApplicationController

resource_controller

end

然而,这里仍有一个小小的瑕疵。 它没有提供标准的 xml 响应能力,但可以用一小段代码来实现:

class StandardsController < ApplicationController

  resource_controller

  index.wants.xml   { render :xml => @standards }



  [new, show].each  do |action|

     action.wants.xml   { render :xml => @standard })

  end

  create.wants.xml  { render :xml => @standard, :status => :created, :location => @standard }

  [update, destroy].each do |action|

     action.wants.xml  { head :ok }

  end

  [create_fails, update_fails].each do |action|

     action.wants.xml { render :xml => @standard.errors, :status => :unprocessable_entity }

  end

end

有了这个插件,写控制器代码如同写模型代码一样,只需加上像resource_controller这样的声明的类方法,以及action.wants之类的“回调”。这个插件自动为控制器的每个方法分配实例变量。在上面的代码中,给 index 方法分配了 @standards ,给其他方法分配了 @standard。

Rails 有一些公用的模式强迫改变控制器代码。其中包括嵌套资源。很容易在 config/routes.rb 中设置路由:

map.resources :organization, :has_many => :standards

但是,一旦你这样做了,你就需要更改控制器来获取和使用上层资源,并在每个动作中正确使用。resource_controller 插件简化了这些。在如上面那样更改路由后,你只需添加一个声明来调用我们的控制器:

class StandardsController < ApplicationController

resource_controller

belongs_to :organization

end

belongs_to 声明允许嵌套资源使用控制器。现在,当一个控制器动作通过嵌套资源 URL 被访问时,例如 /organization/1234 /standards,控制器会自动创建一个名为 @organization 的实例变量,适当地设置好,并使用 standards 所关联地父对象来查找和 建立 Standard 模型的实例。

注意, 同样的控制器也工作在非嵌套的 URL 下,因此我们可以在 routes.rb 中做另一个映射,来允许在 organization 之外访问 starnards:

map.resources :standard

map.resources :organization, :has_many :standards

这样 resource 控制器就会自动地工作在这两种上下文中了。

这个插件也处理命名空间控制器、多态嵌套资源(与 ActiveRecord 多态关联相似和相关)和其他一些奇妙地东西。你也可以获得 URL 以及工作在请求的 URL 上下文中的路径辅助函数。

看来 Resource_controller 是个有用的插件,毫无疑问随着它的成熟,会变得越来越好。细节见James Golicks 的博客。另外还有 Fabio Akita 制作的一段屏幕录像,演示了这个插件的实际使用情况。

查看英文原文:Rails: Resource_controller Plugin Puts Controllers on a Diet

RubyRESTRuby on Rails语言 & 开发架构