将展示逻辑放在控制器中是Ruby中的一个好习惯吗?

11

有些建议 [1] 建议您使用

<%= current_user.welcome_message %>
替代

<% if current_user.admin? %>
  <%= current_user.admin_welcome_message %>
<% else %>
  <%= current_user.user_welcome_message %>
<% end %>

问题在于你必须在代码中的某处有决策逻辑。

我的理解是将决策放在 template 中比放在 controller 更好,因为这可以使你的控制器更加清晰。这个理解正确吗?

有更好的处理方法吗?

http://robots.thoughtbot.com/post/27572137956/tell-dont-ask


1
控制器在这里起什么作用?你的两种情况都没有涉及到控制器。 - Andrew Marshall
10个回答

11

你并不是第一个想知道这个问题的人。如果视图和控制器应该很少或根本没有逻辑,并且模型应该是不涉及呈现的,那么呈现逻辑应该放在哪里呢?

事实证明我们可以使用一种旧技术,称为装饰器模式。其思想是用另一个包含呈现逻辑的类来包装您的模型对象。这个包装类称为装饰器。装饰器将逻辑从您的视图中抽象出来,同时使您的模型与呈现分离。

Draper是一个非常好的宝石,可帮助定义装饰器。

您提供的示例代码可以像下面这样抽象:

在控制器中使用 @user = UserDecorator.new current_user 将装饰器传递给视图。

您的装饰器可能如下所示。

class UserDecorator
  decorates :user

  def welcome_message
    if user.admin?
      "Welcome back, boss"
    else
      "Welcome, #{user.first_name}"
    end
  end
end

你的视图只需要包含@user.welcome_message

注意,模型本身不包含创建消息的逻辑。相反,装饰器会包装模型并将模型数据转换为可呈现的形式。

希望这可以帮到你!


我觉得这个答案不够令人满意。大多数情况下,您会在管理员、普通用户或访客(未登录)上有许多分支,因此您不希望有一个基于 #welcome_message 的装饰器,而是许多对象、一些装饰器和其他可能会响应 #welcome_message 的 NullObjects。这样,您只需要询问一次用户的类型即可。 - hgmnz
如果提问者提到他们有两个用户类,我会创建两个装饰器。相反,我选择保留提供的接口。与通过装饰器进行委托相比,分离数据和表示逻辑所获得的清晰度的代价相当微不足道。 - cjhveal
当然可以,但你现在走的方向不太对。我只是想说,即使到目前为止我只有一个消息需要支持,我也不会以这种方式编写它,但也许这只是我的个人看法。 - hgmnz
2
好的。我只是想以最少的认知摩擦为提问者介绍装饰器的概念。 - cjhveal

5

我会使用一个 helper 辅助完成这个任务。假设你需要基于某个区域设置翻译欢迎消息。

app/helper/user_helper.rb 中编写以下内容:

module UserHelper

  def welcome_message(user)
    if user.admin?
      I18n.t("admin_welcome_message", :name => user.name)
    else
      I18n.t("user_welcome_message", :name => user.name)
    end
  end 

end

在您的视图中,您只需编写以下内容:
<%= welcome_message(user) %>

请注意,装饰器/展示者提供了一种非常干净的面向对象方法,但在我看来,使用助手更简单且足够。

4
不,您不希望在用户类或控制器中使用任何条件语句。博客文章中的示例旨在参考多态性,以传统的面向对象设计为基础。
# in application_controller for example
def current_user
  if signed_in?
    User.find(session[:user_id])
  else
    Guest.new
  end  
end

#app/models/user.rb
class User
   def welcome_message
     "hello #{name}"
   end
end

#app/models/guest.rb
class Guest
  def welcome_message
    "welcome newcomer"
  end
end

你可以创建一个装饰器作为演示者,而不是在模型中添加仅用于表现的方法:

require 'delegate'
class UserPresenter < SimpleDelegator
  def welcome_message
    "hello #{name}"
  end
end

现在current_user看起来像这样:

# application_controller
def current_user
  if signed_in?
    UserPresenter.new(User.find(session[:user_id]))
  else
    Guest.new
  end
end

我认为在模型中查看代码和在控制器中一样糟糕。建议将这些方法放在装饰器中,就像@adriandz在他的答案中提到的那样。 - Tanzeeb Khalili
Tanzeeb,如果你真的读完了我的整个答案,你就会知道我对于将展示逻辑放在模型中的看法是一样的。 - hgmnz
2
你应该将这作为你的实际建议,而不是在结尾处加上一行。OP正在寻求最佳实践,这是你教他的机会。 - Tanzeeb Khalili

3

1
在我看来,如果只有文本发生变化,它就不应该出现在视图中。如果你需要重新构建页面,那就是表现逻辑。这只是数据不同而已。

如果文本需要本地化怎么办? - nathanvda
@nathanvda: 那你来翻译吧。@welcome_notice = I18n.t(@user.admin? ? :welcome_notice_for_admins : :welcome_notice_for_users, :name => @user.name)。文本翻译,除了那些是常量(即可以说是设计的一部分)的内容之外,不应该出现在视图中。 - Amadan
@nathanvda:哦...刚刚看了你的回答,基本上你说的和我一样。那么我不明白你的问题。我们都同意这不应该在视图中。 - Amadan
1
@nathanvda:模型,控制器,装饰器,任何地方都可以,但不包括视图。对于初学者来说,装饰器可能有点难,所以如果在控制器中使用也没关系。这对于模型来说可能有点太呈现了,但即使如此,也比视图好。我的偏好可能是像cjhveal的答案那样,如果需要国际化(i18n),则插入t()调用(尽管我上面快速示例代码中的三个注释是从控制器的角度编写的)。 - Amadan
好的。我会说除了模型以外的任何地方 :) 模型没有访问当前语言的权限,也不应该知道。我不喜欢在控制器中这样做,因为这只是一个细节。我会把它放在帮助程序/装饰器或部分视图中。我猜你更喜欢你的视图完全没有逻辑? - nathanvda
显示剩余2条评论

1
我认为你应该观看有关 Presenters 的 Railscasts 节目以获取答案。

1

在视图中编写逻辑难以维护,我们应该将业务逻辑放在模型中,将所有视图逻辑放在帮助程序中。

如果您希望您的代码以面向对象的方式呈现,请使用装饰器(帮助程序的面向对象方式)

最佳示例:https://github.com/jcasimir/draper


0

将定义current_user.welcome_message的代码放在_app/helpers/application_helper.rb_中,它将可以被任何使用application布局渲染的视图访问。

另一个选择是定义一个自定义帮助器模块,它不一定与给定的视图或控制器相关联(请参见我下面链接的视频),并将其include在您希望具有该功能的视图/控制器的模块中。

这不是非黑即白的事情。但是,从您所描述的内容来看,似乎将此代码放入application_controller.rb中会显得有些突兀,并且它也没有自己控制器的功能,最有效和高效的选项可能是创建一个自定义帮助器模块,并将其包含在您希望具有该功能的帮助器中。尽管如此,这最终是应用程序设计者(即)需要决定的判断调用。

这里是一篇关于帮助器模块的好文章,发布于2011年5月

这里有一个RailsCast,介绍了自定义的帮助模块(即与给定控制器或视图没有必然关联的模块)。简短明了。


使用辅助方法并不是OP链接文章的本意。 - adriandz
@adriandz 对的,但是你在哪里定义 current_user.welcome_message 的逻辑呢?问题并不是在问哪种方法比另一种更好(显然在你的看法中current_user.welcome_message 更好),而是在问这种逻辑应该放到哪里? - rudolph9

0
你可以定义辅助方法来处理这个问题。我认为在模型中写欢迎语句并不是一个好主意,应该在控制器中实现。但你应该尽量让视图中的代码简洁明了,如果可以使用辅助函数帮助实现,则应该使用。

0

一个好的实践是拥有真实的View实例。Rails对MVP的恶搞(确实有区别,请查阅)不幸地似乎假装视图是模板。这是错误的。

在MVC和MVC-inspired模式下,视图应该包含表示逻辑。它们还应该操作多个模板,并根据模型层的状态和信息来决定使用哪些模板来表示(是的,模型是一层而不是ORM实例)。

因此,回答这个问题:表示逻辑在控制器中没有任何位置。


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