245

我真的很喜欢 Rails(尽管我通常是 RESTless),而且我喜欢 Ruby 非常 OO。尽管如此,创建大型 ActiveRecord 子类和大型控制器的趋势是很自然的(即使您确实为每个资源使用了一个控制器)。如果您要创建更深层次的对象世界,您会将类(和模块,我想)放在哪里?我问的是视图(在 Helpers 本身中?)、控制器和模型。

Lib 没问题,我找到了一些解决方案让它在开发环境中重新加载,但我想知道是否有更好的方法来做这些事情。我真的只是担心类变得太大。另外,引擎怎么样?它们如何适应?

4

4 回答 4

384

因为 Rails 根据 MVC 提供结构,所以很自然地最终使用为您提供的模型、视图和控制器容器。初学者(甚至一些中级程序员)的典型习惯用法是将应用程序中的所有逻辑都塞进模型(数据库类)、控制器或视图中。

在某些时候,有人指出“胖模型,瘦控制器”范式,中级开发人员匆忙从控制器中删除所有内容并将其扔到模型中,这开始成为应用程序逻辑的新垃圾箱。

事实上,瘦控制器是一个好主意,但推论——将所有东西都放在模型中,并不是最好的计划。

在 Ruby 中,您有几个很好的选择可以让事情变得更加模块化。一个相当流行的答案是只使用lib包含方法组的模块(通常隐藏在 中),然后将模块包含到适当的类中。这在您希望在多个类中重用功能类别但功能仍然附加到类的情况下会有所帮助。

请记住,当您将一个模块包含到一个类中时,这些方法将成为该类的实例方法,因此您最终仍然会得到一个包含大量方法的类,它们只是很好地组织到多个文件中。

这个解决方案在某些情况下可以很好地工作——在其他情况下,您将需要考虑在代码中使用不是模型、视图或控制器的类。

考虑它的一个好方法是“单一责任原则”,它表示一个类应该对单一(或少量)事物负责。您的模型负责将数据从应用程序保存到数据库。您的控制器负责接收请求并返回可行的响应。

如果您有不完全适合这些框的概念(持久性、请求/响应管理),您可能想考虑如何对相关想法进行建模。您可以将非模型类存储在 app/classes 或其他任何位置,然后通过执行以下操作将该目录添加到您的加载路径:

config.load_paths << File.join(Rails.root, "app", "classes")

如果您使用乘客或 JRuby,您可能还希望将您的路径添加到急切加载路径:

config.eager_load_paths << File.join(Rails.root, "app", "classes")

底线是,一旦你在 Rails 中发现自己提出这个问题,是时候加强你的 Ruby 技能并开始建模类,而不仅仅是 Rails 默认为你提供的 MVC 类。

更新:这个答案适用于 Rails 2.x 及更高版本。

于 2009-07-01T21:35:06.240 回答
62

更新:使用 Concerns 已被确认为 Rails 4 中的新默认设置

这实际上取决于模块本身的性质。我通常将控制器/模型扩展放在 app 内的 /concerns 文件夹中。

# concerns/authentication.rb
module Authentication
  ...
end    

# controllers/application_controller.rb
class ApplicationController
  include Authentication
end



# concerns/configurable.rb
module Configurable
  ...
end    

class Model 
  include Indexable
end 

# controllers/foo_controller.rb
class FooController < ApplicationController
  include Indexable
end

# controllers/bar_controller.rb
class BarController < ApplicationController
  include Indexable
end

/lib 是我对通用库的首选。我总是在 lib 中有一个项目名称空间,我将所有特定于应用程序的库放在其中。

/lib/myapp.rb
module MyApp
  VERSION = ...
end

/lib/myapp/CacheKey.rb
/lib/myapp/somecustomlib.rb

Ruby/Rails 核心扩展通常发生在配置初始化程序中,因此库只在 Rails boostrap 上加载一次。

/config/initializer/config.rb
/config/initializer/core_ext/string.rb
/config/initializer/core_ext/array.rb

对于可重用的代码片段,我经常创建(微)插件,以便我可以在其他项目中重用它们。

当对象打算由助手使用时,助手文件通常包含助手方法,有时还包含类(例如表单构建器)。

这是一个非常笼统的概述。如果您想获得更多定制建议,请提供有关具体示例的更多详细信息。:)

于 2009-07-01T12:15:30.680 回答
10

...制作大型 ActiveRecord 子类和大型控制器的趋势是很自然的...

“巨大”是一个令人担忧的词... ;-)

您的控制器如何变得庞大?这是您应该关注的:理想情况下,控制器应该很薄。凭空挑选一个经验法则,我建议如果每个控制器方法(操作)经常有超过 5 或 6 行代码,那么你的控制器可能太胖了。是否存在可以进入辅助函数或过滤器的重复项?是否有可以下推到模型中的业务逻辑?

你的模型是如何变得庞大的?您是否应该寻找减少每个班级的责任数量的方法?您可以将任何常见行为提取到 mixins 中吗?或者您可以委托给辅助类的功能领域?

编辑:试图扩大一点,希望不要太严重地扭曲任何东西......

Helpers:住​​在里面app/helpers,主要用于让视图更简单。它们要么是特定于控制器的(也可用于该控制器的所有视图),要么是普遍可用的(module ApplicationHelper在 application_helper.rb 中)。

过滤器:假设您在多个操作中具有相同的代码行(通常是使用params[:id]或类似方法检索对象)。这种重复可以首先被抽象为一个单独的方法,然后通过在类定义中声明一个过滤器来完全退出操作,例如before_filter :get_object. 请参阅ActionController Rails 指南中的第 6 节让声明式编程成为您的朋友。

重构模型更像是一种宗教性的事情。例如,鲍勃叔叔的门徒会建议您遵守SOLID的五诫。Joel & Jeff可能会推荐一种更、呃、“务实”的方法,尽管他们后来确实看起来更和解了。在一个类中找到一个或多个对其明确定义的属性子集进行操作的方法是尝试识别可能从 ActiveRecord 派生模型中重构出来的类的一种方法。

顺便说一下,R​​ails 模型不必是 ActiveRecord::Base 的子类。或者换一种说法,模型不必是表格的模拟物,甚至根本不需要与存储的任何东西相关。更好的是,只要您app/models按照 Rails 的约定命名文件(在类名上调用 #underscore 以找出 Rails 将查找的内容),Rails 会找到它而无需任何requires。

于 2009-07-01T14:08:24.150 回答
2

这是一篇关于重构似乎源自“瘦控制器”哲学的胖模型的优秀博客文章:

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

基本信息是“不要从 Fat Models 中提取 Mixins”,改用服务类,作者提供了 7 种模式来做到这一点

于 2015-09-15T07:58:45.877 回答