Rails中的OO设计:放置东西的位置

Dan*_*ark 242 ruby directory-structure ruby-on-rails

我真的很喜欢Rails(尽管我通常都是RESTless),而且我非常喜欢Ruby.仍然,制作巨大的ActiveRecord子类和巨大的控制器的趋势是很自然的(即使你每个资源都使用一个控制器).如果你要创建更深层次的对象世界,你会在哪里放置类(和模块,我想)?我问的是(帮助者自己?),控制器和模型的观点.

Lib是可以的,我找到了一些解决方案,让它在开发环境中重新加载,但我想知道是否有更好的方法来做这些事情.我真的只是担心课程变得太大了.那么,Engines怎么样?它们如何适应?

Yeh*_*atz 381

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

在某些时候,有人指出"胖模型,瘦 - 控制器"范例,中间开发人员匆匆从控制器中删除所有内容并将其投入到模型中,模型开始成为应用程序逻辑的新垃圾箱.

实际上,Skinny控制器是一个好主意,但推论 - 将所有内容都放在模型中,并不是最好的计划.

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

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

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

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

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

config.load_paths << File.join(Rails.root, "app", "classes")
Run Code Online (Sandbox Code Playgroud)

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

config.eager_load_paths << File.join(Rails.root, "app", "classes")
Run Code Online (Sandbox Code Playgroud)

最重要的是,一旦你在Rails中找到了自己提出这个问题的观点,就应该加强你的Ruby chops并开始建模类,这些类不仅仅是Rails默认提供给你的MVC类.

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

  • 对于更新的版本,config.autoload_paths默认为app下的所有目录.因此,您无需像上面所述更改config.load_paths.我不确定eager_load_paths(还),需要调查一下.有谁知道吗? (33认同)
  • 如果Rails附带这个"classes"文件夹以鼓励"单一责任原则"并使开发人员能够创建非数据库支持的对象,那将是很好的.Rails 4中的"Concerns"实现(参见Simone的回答)似乎已经开始实现模块以跨模型共享逻辑.但是,没有为非数据库支持的纯Ruby类创建此类工具.鉴于Rails非常自以为是,我很好奇不包括像这样的文件夹背后的思考过程? (8认同)
  • 如果它仍然适用于Rails 3,你能否更新你的答案? (4认同)

Sim*_*tti 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
Run Code Online (Sandbox Code Playgroud)

/ lib是我对通用库的首选.我总是在lib中有一个项目命名空间,我放置了所有特定于应用程序的库.

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

/lib/myapp/CacheKey.rb
/lib/myapp/somecustomlib.rb
Run Code Online (Sandbox Code Playgroud)

Ruby/Rails核心扩展通常在配置初始化器中进行,因此库只在Rails boostrap上加载一次.

/config/initializer/config.rb
/config/initializer/core_ext/string.rb
/config/initializer/core_ext/array.rb
Run Code Online (Sandbox Code Playgroud)

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

辅助文件通常包含辅助方法,有时候当对象打算由助手使用时(例如Form Builders).

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

  • 使用关注点是关于 (2认同)

Mik*_*use 10

...制作巨大的ActiveRecord子类和巨大的控制器的倾向是很自然的......

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

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

你的模型如何变得庞大?你是否应该考虑减少每堂课的责任?您是否可以将任何常见行为提取到mixins中?或者您可以委派给辅助类的功能区域?

编辑:试图扩大一点,希望不要扭曲任何太糟糕的...

助手:住在app/helpers并且主要用于使观点更简单.它们或者是控制器特定的(也可用于该控制器的所有视图)或通常可用(module ApplicationHelper在application_helper.rb中).

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

重构模型更具宗教性.例如,Bob叔叔的弟子会建议您遵循SOLID的五条诫命.乔尔和杰夫可能会推荐一种更加"笨拙"的"务实"方法,尽管他们后来看起来似乎更加和解了.在类中查找一个或多个操作明确定义的属性子集的方法是尝试识别可能从ActiveRecord派生模型中重构的类的一种方法.

顺便说一句,Rails模型不必是ActiveRecord :: Base的子类.或者换句话说,模型不必是表格的模拟,甚至与存储的任何东西相关.更好的是,只要app/models根据Rails的约定命名文件(在类名上调用#underscore以找出Rails将要查找的内容),Rails就会在没有任何require必要的情况下找到它.