`
biyeah
  • 浏览: 201382 次
  • 来自: ...
社区版块
存档分类
最新评论

[转]Skinny Controller, Fat Model

 
阅读更多
不错的文章,出处:http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
When first getting started with Rails, it is tempting to shove lots of logic in the view. I’ll admit that I was guilty of writing more than one template like the following during my Rails novitiate:
<!-- app/views/people/index.rhtml -->
<% people = Person.find(
      :conditions => ["added_at > ? and deleted = ?", Time.now.utc, false],
      :order => "last_name, first_name") %>
<% people.reject { |p| p.address.nil? }.each do |person| %>
  <div id="person-<%= person.new_record? ? "new" : person.id %>">
    <span class="name">
      <%= person.last_name %>, <%= person.first_name %>
    </span>
    <span class="age">
      <%= (Date.today - person.birthdate) / 365 %>
    </span>
  </div>
<% end %>

Not only is the above difficult to read (just you try and find the HTML elements in it), it also completely bypasses the “C” in “MVC”. Consider the controller and model implementations that support that view:
# app/controllers/people_controller.rb
class PeopleController < ActionController::Base
end

# app/models/person.rb
class Person < ActiveRecord::Base
  has_one :address
end

Just look at that! Is it really any wonder that it is so tempting for novices to take this approach? They’ve got all their code in one place, and they don’t have to go switching between files to follow the logic of their program. Also, they can pretend that they haven’t actually written any Ruby code; I mean, look, it’s just the template, right?

For various reasons, though, this is a very, very bad idea. MVC has been successful for many reasons, and some of those reasons are “readability”, “maintainability”, “modularity”, and “separation of concerns”. You’d like your code to have those properties, right?

A better way is to move as much of the logic as possible into the controller. Seriously, isn’t that what the controller is for? It is supposed to mediate between the view and the model. Let’s make it earn its right to occupy a position in our source tree:

<!-- app/views/people/index.rhtml -->
<% @people.each do |person| %>
  <div id="person-<%= person.new_record? ? "new" : person.id %>">
    <span class="name">
      <%= person.last_name %>, <%= person.first_name %>
    </span>
    <span class="age">
      <%= (Date.today - person.birthdate) / 365 %>
    </span>
  </div>
<% end %>


# app/controllers/people_controller.rb
class PeopleController < ActionController::Base
  def index
    @people = Person.find(
      :conditions => ["added_at > ? and deleted = ?", Time.now.utc, false],
      :order => "last_name, first_name")
    @people = @people.reject { |p| p.address.nil? }
  end
end

Better! Definitely better. We dropped that big noisy chunk at the top of the template, and it’s more immediately obvious what the structure of the HTML file is. Also, you can see by reading the controller code roughly what kind of data is going to be displayed.

However, we can do better. There’s still a lot of noise in the view, mostly related to conditions and computations on the model objects. Let’s pull some of that into the model:

# app/models/person.rb
class Person < ActiveRecord::Base
  # ...

  def name
    "#{last_name}, #{first_name}"
  end

  def age
    (Date.today - person.birthdate) / 365
  end

  def pseudo_id
    new_record? ? "new" : id
  end
end


<!-- app/views/people/index.rhtml -->
<% @people.each do |person| %>
  <div id="person-<%= person.pseudo_id %>">
    <span class="name"><%= person.name %></span>
    <span class="age"><%= person.age %></span>
  </div>
<% end %>

Wow. Stunning, isn’t it? The template is reduced to almost pure HTML, with only a loop and some simple insertions sprinkled about. Note, though, that this is not just a cosmetic refactoring: by moving name, age and pseudo_id into the model, we’ve made it much easier to be consistent between our views, since any time we need to display a person’s name or age we can simply call those methods and have them computed identically every time. Even better, if we should change our minds and decide that (e.g.) age needs to be computed differently, there is now only one place in our code that needs to change.

However, there’s still a fair bit of noise in the controller. I mean, look at that index action. If you were new to the application, coming in to add a new feature or fix a bug, that’s a lot of line noise to parse just to figure out what is going on. If we abstract that code into the model, we can not only slim the controller down, but we can effectively document the operation we’re doing by naming the method in the model appropriately. Behold:


# app/models/person.rb
class Person < ActiveRecord::Base
  def self.find_recent
    people = find(
      :conditions => ["added_at > ? and deleted = ?", Time.now.utc, false],
      :order => "last_name, first_name")
    people.reject { |p| p.address.nil? }
  end

  # ...
end


# app/controllers/people_controller.rb
class PeopleController < ActionController::Base
  def index
    @people = Person.find_recent
  end
end

Voila! Looking at PeopleController#index, you can now see immediately what is going on. Furthermore, in the model, that query is now self-documenting, because we gave the method a descriptive name, find_recent. (If you wanted, you could even take this a step further and override the find method itself, as I described in Helping ActiveRecord finders help you. Then you could do something like Person.find(:recent) instead of Person.find_recent. There’s not a big advantage in that approach in this example, so it mostly depends on what you prefer, esthetically.)

Be aggressive! Try to keep your controller actions and views as slim as possible. A one-line action is a thing of wonder, as is a template that is mostly HTML. It is also much more maintainable than a view that is full of assignment statements and chained method calls.

Another (lesser) nice side-effect of lean controllers: it allows respond_to to stand out that much more, making it simple to see at a glace what the possible output types are:

# app/controllers/people_controller.rb
class PeopleController < ActionController::Base
  def index
    @people = Person.find_recent

    respond_to do |format|
      format.html
      format.xml { render :xml => @people.to_xml(:root => "people") }
      format.rss { render :action => "index.rxml" }
    end
  end
end

Give all this a try in your next project. Like adopting RESTful practices, it may take some time to wrap your mind around the refactoring process, especially if you’re still accustomed to throwing lots of logic in the view. Just be careful not to go too far; don’t go putting actual view logic in your model. If you find your model rendering templates or returning HTML or Javascript, you’ve refactored further than you should. In that case, you should make use of the helper modules that script/generate so kindly stubs out for you in app/helpers. Alternatively, you could look into using a presenter object.
分享到:
评论

相关推荐

    AS_Skinny_V3.9

    AS_Skinny_V3.9

    基于SKINNY算法的持久故障攻击

    基于SKINNY算法的持久故障攻击,徐岳,孙斌,SKINNY算法是一种SPN结构的轻量级可调分组密码算法,由Beierle等人在CRYPTO2016上提出。SKINNY是一种采用可调密钥的分组密码算法,根据参数�

    skinny-thymeleaf_2.11-1.3.0.zip

    【标题】"skinny-thymeleaf_2.11-1.3.0.zip" 指的是一款集成Thymeleaf模板引擎的轻量级Scala Web框架Skinny的扩展包,适用于Scala 2.11版本,版本号为1.3.0。Skinny框架是针对Scala开发者设计的,它提供了类似Spring...

    TDM Solutions Skinny3D(3D模型压缩) V0.9.1 中文无限制版.zip

    TDM Solutions Skinny3D(3d模型压缩)是一款3D模型瘦身软件,是3D打印的最佳帮手!您的3D模型文件太大不方便传送、携带而伤脑筋吗?只要将您的STL或OBJ文件开启后按一个按钮,即可将文件大小缩减为原有大小的十分之一...

    skinny-common_2.11-1.3.4.zip

    标题中的"skinny-common_2.11-1.3.4.zip"是一个软件库的压缩包,通常在Java或Scala开发中使用。这个库可能是Skinny Framework的一部分,这是一个轻量级、基于Scala的Web应用框架,它允许开发者快速构建高效、可维护...

    skinny-assets_2.10-1.0.14.zip

    皮肤瘦资产(skinny-assets_2.10-1.0.14.zip)看起来是一个与Scala相关的开源项目,可能是用于构建轻量级Web应用程序时管理静态资源的工具。然而,由于提供的信息有限,我们将主要关注描述中提及的"spoiwo.zip",这是...

    ASP.NET.MVC.5.with.Bootstrap.and.Knockout.js.1491914394

    Fat Model, Skinny Controller Part IV. A Practical Example Chapter 13. Building a Shopping Cart Chapter 14. Building the Data Model Chapter 15. Implementing the Layout Chapter 16. Lists of Books ...

    skinny-test_2.11-1.2.7.zip

    "skinny-test_2.11-1.2.7.zip" 文件中包含的 "astyanax.zip" 是一个针对NETFLIX CasdRaJava客户端的开源项目——ASTYANAX,它主要用于Cassandra数据库的交互。本文将深入探讨ASTYANAX项目,以及如何利用其进行CQL...

    skinny-http-client_2.10-1.1.8.zip

    "skinny-http-client_2.10-1.1.8.zip"这个压缩包文件包含了http4s的一个特定版本,即1.1.8,它是针对Scala 2.10编译的。http4s以其精简的设计理念和强大的功能,赢得了广大开发者的好评。 http4s的核心是构建在 ...

    skinny-assets_2.10-1.0.0-RC9.zip

    【标题】"skinny-assets_2.10-1.0.0-RC9.zip" 指的是一款名为 Skinny Assets 的开源项目,版本号为 2.10 的第 1.0.0 发布候选版(Release Candidate 9)。Skinny Assets 可能是一个针对 Groovy 语言或者与 Groovy ...

    Skinny:简单PHP模板引擎

    Skinny是一个单文件高速模板引擎,具有“简单”的概念,其功能可以承受实际水平。 原始和。 作者佐佐木淳一(作者名称:Kuasuki) 被安排的柳条翼(Wataru Kanzaki) 版本0.3.2 + 1.0.2 2015/10/22 修改部分 删除...

    dotnet-Skinny一个轻量级的基于NetCore编写的PostgresORM

    Skinny是一个针对.Net Core平台设计的轻量级对象关系映射(ORM)框架,专为PostgreSQL数据库系统而构建。它的主要目标是提供简洁、高效且易于使用的数据访问层,帮助开发者快速地进行数据操作,减少对底层SQL的依赖...

    skinny-framework_2.10-1.2.4.zip

    Skinny Framework 2.10-1.2.4 是一个针对Scala编程语言的轻量级Web框架,它旨在提供高效、简洁的开发体验。这个压缩包“skinny-framework_2.10-1.2.4.zip”包含了该框架的一个特定版本,适合于Scala 2.10平台。...

    skinny-json_2.10-2.0.2.zip

    皮肤瘦JSON库(Skinny JSON)是一个轻量级的库,专为Scala 2.10设计,版本为2.0.2,被封装在"skinny-json_2.10-2.0.2.zip"这个压缩包中。这个库的主要目标是提供一个简洁、高效的JSON处理方案,使得开发人员在Scala...

    skinny-validator_2.11-1.1.0.RC2.zip

    【标题】"skinny-validator_2.11-1.1.0.RC2.zip" 指的是一个名为 "skinny-validator" 的开源项目,它可能是针对Scala 2.11版本的一个验证库,其版本号是1.1.0的候选发布版2(RC2)。这个库可能提供了轻量级的验证...

Global site tag (gtag.js) - Google Analytics