Rails中的OO设计:在哪里放置东西


244

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

Lib没关系,我已经找到了一些解决方案可以在开发环境中重新加载它,但我想知道是否有更好的方法可以做到这一点。我真的只是担心班级过大。此外,引擎又如何适应引擎呢?

Answers:


384

由于Rails提供了基于MVC的结构,因此自然而然地使用为您提供的模型,视图和控制器容器。对于初学者(甚至是一些中级程序员)而言,典型的习惯用法是将应用程序中的所有逻辑塞入模型(数据库类),控制器或视图中。

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

瘦控制器实际上是一个好主意,但是将模型中的所有内容推论得出的推论并不是真正的最佳计划。

在Ruby中,您有几个不错的选择,可以使事情更加模块化。一个比较流行的答案是只使用lib包含方法组的模块(通常藏在中),然后将模块包含在适当的类中。如果您有希望在多个类中重用的功能类别,但仍在功能上仍附加在类上的情况下,这将很有帮助。

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

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

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

如果您有不完全满足需要的那些箱子(持久性,请求/响应管理)的概念,你可能要考虑如何有问题的想法建模。您可以在应用程序/类中或其他任何位置存储非模型类,然后通过执行以下操作将该目录添加到加载路径中:

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

如果您使用的是Passenger或JRuby,则可能还希望将路径添加到热切的加载路径中:

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

最重要的是,一旦到达Rails的某个位置,您就会发现自己在问这个问题,现在该增强您的Ruby Chop并开始建模不仅仅是Rails默认为您提供的MVC类的类。

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


天啊 我没有为非模型添加单独的目录。我可以整理一下……
Mike Woodhouse

耶胡达,谢谢。好答案。这正是我在继承的应用程序(以及我制作的应用程序)中看到的内容:控制器,模型,视图以及自动为控制器和视图提供的帮助器中的所有内容。然后来自lib的mixins,但是从来没有尝试过进行真正的OO建模。不过,您是对的:在“应用/类或其他任何地方”。只是想检查我是否缺少一些标准答案……
Dan Rosenstark

33
在更新的版本中,config.autoload_paths默认为app下的所有目录。因此,您无需如上所述更改config.load_paths。我不确定eager_load_paths(尚未),需要对此进行调查。有人知道吗?
Shyam Habarakada 2012年

被动攻击中间体:P
塞巴斯蒂安·帕滕

8
如果Rails带有此“ classes”文件夹,则鼓励“单一责任原则”并使开发人员能够创建不受数据库支持的对象,那就太好了。Rails 4中的“关注点”实现(请参见Simone的答案)似乎已经在照顾实现模块以在模型之间共享逻辑的情况。但是,尚未为没有数据库支持的普通Ruby类创建此类工具。考虑到Rails非常自以为是,我很好奇不包括这样的文件夹背后的思考过程?
瑞安·弗朗西斯

62

更新在Rails 4中,确认使用关注点作为新的默认设置

这实际上取决于模块本身的性质。我通常将控制器/模型扩展名放在应用程序的/ 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

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

帮助对象(例如,表单生成器)打算使用该对象时,帮助文件通常包含帮助方法,有时还包含类。

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


奇怪的事情。我无法使用require_dependency RAILS_ROOT +“ / lib / my_module”来处理lib目录中的某些内容。它肯定会执行并抱怨找不到文件,但不会重新加载它。
丹·罗森斯塔克

Ruby只需要加载一次。如果要无条件加载某些内容,请使用load。
Chuck

另外,对于我来说,在一个应用实例的生存期内要两次加载一个文件似乎很不寻常。您正在生成代码吗?
Chuck

为什么使用require_dependency而不是require?还要注意,如果遵循命名约定,则根本不需要使用require。如果在lib / my_module中创建MyModule,则可以在没有以前的require的情况下调用MyModule(即使使用require应该更快,并且有时更具可读性)。另请注意,/ lib中的文件仅在引导程序中加载一次。
西蒙妮·卡列蒂

1
关注的使用令人担忧
bbozo

10

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

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

您的控制器如何变得庞大?您应该看一下:理想情况下,控制器应该薄。我凭空选择一个经验法则,建议您如果每个控制器方法(动作)经常有5行或6行以上的代码,则您的控制器可能太胖了。是否存在可能移入辅助功能或过滤器的重复项?是否存在可以推入模型的业务逻辑?

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

编辑:尝试扩展一点,希望不会扭曲得太严重...

助手:居住在这里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便会找到它,而无需进行任何操作require


迈克,在所有方面都是对的,谢谢您的关心……我继承了一个项目,其中控制器上有一些方法非常庞大。我已经将它们分解为较小的方法,但是控制器本身仍然很“胖”。因此,我正在寻找所有减轻负担的选项。您的答案是“帮助器函数”,“过滤器”,“模型”,“ mixins”和“帮助器类”。那么,我可以把这些东西放在哪里?我可以组织一个在开发环境中自动加载的类层次结构吗?
丹·罗森斯塔克

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.