Rails的模型(Model)、视图(View)、控制器(Controller)和辅助器(Helper):它们各自负责什么?

155
在 Ruby on Rails 开发(或 MVC 框架一般情况下),我应该遵循什么快速规则来确定代码逻辑的放置位置。
请肯定回答,用 要把这个放在这里,而不是 不要把那个放在那里
10个回答

173

MVC

控制器(Controller):在这里放置与用户需求有关的代码,并决定要给他们什么,确定他们是否已登录,是否应该看到某些数据等。最终,控制器查看请求并确定要显示哪些数据(模型)和渲染哪些视图。如果您对代码是否应该放在控制器中有疑问,那么它可能不应该放在那里。保持您的控制器skinny

视图(View):视图应只包含最少量的代码来显示数据(模型),它不应执行大量处理或计算,它应该显示由模型计算(或汇总)或由控制器生成的数据。如果您的视图确实需要执行无法由模型或控制器完成的处理,请将代码放在Helper中。视图中的大量Ruby代码使页面标记难以阅读。

模型(Model):您的模型应该是所有与数据相关的代码(构成您网站的实体,例如用户、帖子、帐户、朋友等)所在的地方。如果代码需要保存、更新或汇总与实体相关的数据,请将其放在此处。它将在您的视图和控制器中可重复使用。


2
人们开始逐渐远离臃肿的模型。我喜欢把我的模型看作是一个数据结构。然后,我编写一些 Ruby 对象来实现这种行为,并使用模型进行初始化(它将模型视为数据,就像您可能在 Rails 之外的对象中将字符串和数组视为数据一样)。这里有一个很好的视频,其中包含了这种技术的示例。 - Joshua Cheek
@AdamDonahue 我不确定肥胖的任何事情都可以被视为好事。许多责任最好属于服务。 - fatuhoku

38

补充pauliephonic的回答:

Helper:功能函数,使视图创建更加容易。例如,如果您经常迭代一个小部件列表来显示其价格,请将其放入助手中(以及实际显示的部分)。或者,如果您有一段不想混杂在视图中的RJS代码,请将其放入助手中。


实际上我们也可以把 sign_in 方法放在 Helper 里面吗?就像 RoR 教程在这里建议的那样 >>>http://ruby.railstutorial.org/book/ruby-on-rails-tutorial#sec-a_working_sign_in_method - Ivan Wang

14

MVC模式实际上只涉及UI,不牵扯其他内容。你不应该在控制器(controller)中添加任何复杂的业务逻辑,因为它只控制视图而不是逻辑。控制器应关注于选择正确的视图并将更复杂的内容委托给领域模型(Model)或业务层。

领域驱动设计(Domain Driven Design)有一个称为服务(Services)的概念,那是您放置需要编排许多不同类型对象的逻辑的地方,通常意味着不自然归属于模型类(Model class)的逻辑。

我通常认为服务层(Service layer)是我的应用程序的API。我的服务层通常与我正在创建的应用程序的需求非常接近,因此服务层作为更低级别的应用程序中找到的更复杂交互的简化,也就是说,您可以跳过服务层完成相同的目标,但是要使其正常工作,必须操作更多的杠杆。

请注意,这里我不是在谈论Rails,而是在谈论解决您特定问题的一般架构风格。


这是一个很棒的答案 :) - Carlos Martinez

12

7

7
在控制器中放置与授权/访问控制相关的内容。
模型是关于数据的。验证、关系、CRUD、业务逻辑。
视图是关于展示数据的。仅显示和获取输入。
控制器是关于控制数据从模型到视图(以及哪个视图),以及从视图到模型。控制器也可以在没有模型的情况下存在。
我喜欢把控制器想象成一个安保/前台接待员,他会将你这位顾客(请求)引导到适当的柜台,在那里你可以向出纳员(视图)提问。然后,出纳员(视图)去找经理(模型)拿到答案,而你则不会见到经理。然后,请求会回到安保/前台接待员(控制器)处等待,直到被引导到另一个出纳员(视图),后者会告诉你经理(模型)根据另一个出纳员(视图)的问题所回答的答案。
同样地,如果你想告诉出纳员(视图)一些信息,基本上会发生相同的事情,只是第二个出纳员会告诉你经理是否接受了你的信息。此外,安保/前台接待员(控制器)可能会告诉你滚蛋,因为你没有权限告诉经理那些信息。
因此,为了扩展这个比喻,在我刻板印象和不现实的世界中,出纳员(视图)非常漂亮但头脑空空,经常相信你告诉他们的任何事情;安保/前台接待员礼貌到位,但知识不是很丰富,只知道人们应该去哪里,不应该去哪里;而经理非常丑陋而且凶恶,但知道所有的事情,能够分辨什么是真的,什么是假的。

4

一个有助于正确分离的方法是避免“从控制器传递本地变量到视图”的反模式。而不是这样:

# app/controllers/foos_controller.rb:
class FoosController < ApplicationController

  def show
    @foo = Foo.find(...)
  end

end

#app/views/foos/show.html.erb:
...
<%= @foo.bar %>
...

尝试将其移动到可用作辅助方法的getter中:
# app/controllers/foos_controller.rb:
class FoosController < ApplicationController

  helper_method :foo

  def show
  end

  protected

  def foo
    @foo ||= Foo.find(...)
  end

end

#app/views/foos/show.html.erb:
...
<%= foo.bar %>
...

这样做可以更轻松地修改放入 "@foo" 中的内容以及使用方式。它增加了控制器和视图之间的分离,而不会使它们变得更加复杂。

2
  1. Rails会执行很多魔法来将控制器的实例变量复制到视图实例中。2) 建议的实现方式只有在访问foo时才加载它,这可能会节省一些工作时间。但真正重要的答案是第一点。
- webmat
11
叹气 这太糟糕了。Rails实例变量共享是一种特性而不是反模式。这是一种普遍存在、低认知负担的语法糖,很少会引起真实世界的问题。如果您不喜欢它,那很好,但是用一个复杂的非标准结构来绕过它将使事情变得更糟。在这种情况下,您实际上正在将foo变成全局变量(至少在每个控制器中是全局的)。试图通过显著增加作用域来纠正对变量作用域的滥用是极具讽刺意味的。 - gtd
1
我不相信,dasil003。foo@foo的作用域是相同的 - 它们都被限定在<ControllerClass,request>对中。此外,通过使用getter版本,我可以更改如何找到/存储/缓存Foo对象,而不更改视图访问它的方式。 - James A. Rosen
1
我认为你指的是“传递实例变量”反模式。一个实例变量会在整个渲染过程中泄露状态,即使在深度嵌套的局部中也是如此。你的解决方案也会泄露状态,但比实例变量略好,因为它不允许重新赋值。传递本地变量实际上是最好的,因为它就像调用方法一样;局部变量对局部是不可见的。请参见这个答案 - Kelvin
这个答案中的代码在“坏”版本和“好”版本中都没有任何局部变量。你所说的局部变量是什么意思? - Andrew Grimm
显示剩余3条评论

2

嗯,这在某种程度上取决于逻辑需要处理的内容...

通常情况下,将更多的东西推入你的模型中,让控制器保持简单是有意义的。这确保了该逻辑可以轻松地从任何需要访问所表示模型数据的地方使用。视图应该几乎不包含逻辑。因此,总的来说,你应该努力使其不重复自己。

此外,快速搜索一下谷歌就会发现一些更具体的例子。

模型:验证要求、数据关系、创建方法、更新方法、删除方法、查找方法(请注意,你不仅应该拥有这些方法的通用版本,而且如果有一些你经常做的事情,比如按姓氏查找红头发的人,那么你应该提取该逻辑,这样你只需调用find_redH_by_name("smith")之类的方法即可)

视图:应该全部关注数据格式化,而不是数据处理。

控制器:这是数据处理的地方。来自互联网的解释:“控制器的目的是响应用户请求的操作,获取用户设置的任何参数,处理数据,与模型交互,然后将请求的数据以最终形式传递给视图。”

希望这能有所帮助。


0
简而言之,通常模型将包含所有与表格相关的代码,它们的简单或复杂关系(将其视为涉及多个表格的SQL查询),通过业务逻辑对数据/变量进行操作以得出结果。 控制器将具有指向所请求工作的相关模型的代码/指针。 视图将接受用户输入/交互并显示相应的响应。
任何与此相差较大的主要偏差都会对该部分造成不必要的压力,从而可能影响整个应用程序的性能。

-1

测试,测试... 尽可能将逻辑放在模型中,然后您将能够正确地测试它。单元测试通过测试模型来测试数据及其形成方式,功能测试通过测试控制器来测试路由或控制方式,因此,除非数据在模型中,否则无法测试数据的完整性。

j


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接