我开始欣赏Rails中的“瘦调控制器”哲学,它认为业务逻辑不应该在控制器中,但它们应该基本上只负责调用一些模型方法,然后决定渲染/重定向的内容。将业务逻辑推入模型(或其他地方)可以保持操作方法的清洁(并避免在控制器的功能测试中存根长链的ActiveRecord方法)。
我碰到的大多数情况都是这样的:我有三个模型,Foo
,Bar
和Baz
。他们每个人都有一个定义的方法或范围(称之为filter
),将对象缩小到我正在寻找的范围。一个瘦的动作方法可能如下所示:
def index
@foos = Foo.filter
@bars = Bar.filter
@bazs = Baz.filter
end
但是,我遇到了一个视图需要显示更多层次结构的数据结构的情况。例如,Foo
has_many bars
和Bar
has_many bazs
。在视图(一般的“信息中心”页面)中,我将显示类似的内容,其中每个foo
,bar
和baz
都已按照一些标准进行过滤(例如,对于每个级别,我只想显示active
个:
Foo1 - Bar1 (Baz1, Baz2)
Bar2 (Baz3, Baz4)
-----------------------
Foo2 - Bar3 (Baz5, Baz6)
Bar4 (Baz7, Baz8)
为了向视图提供所需的数据,我最初的想法是在控制器中放置这样的东西:
def index
@data = Foo.filter.each_with_object({}) do |foo, hash|
hash[foo] = foo.bars.filter.each_with_object({}) do |bar, hash2|
hash2[bar] = bar.bazs.filter
end
end
end
我可以将其推向Foo
模型,但这并不是更好。这似乎不是一个复杂的数据结构,值得分解为一个单独的非ActiveRecord模型或类似的东西,它只是取一些foos
及其bars
和它们的bazs
与每个步骤都应用一个非常简单的过滤器。
将这样的分层数据从控制器传递到视图的最佳做法是什么?
答案 0 :(得分:3)
你可以这样得到@foos
:
@foos = Foo.filter.includes(:bars, :bazs).merge(Bar.filter).merge(Baz.filter).references(:bars, :bazs)
现在您的关系被过滤并急切加载。您想要做的其余事情是关注您希望如何在视图中呈现它。也许你会做这样的事情:
<% Foo.each do |foo| %>
<%= foo.name %>
<% foo.bars.each do |bar| %>
<%= bar.name %>
<% bar.bazs.each do |baz| %>
<%= baz.name %>
<% end %>
<% end %>
<% end %>
控制器中的任何类型的哈希构建都是不必要的。您在视图中使用的抽象级别是合理的。
答案 1 :(得分:1)
如果提取表单对象,这种事情被广泛接受的最佳实践之一。来自Code Climate的Bryan Helmkamp撰写了一篇非常好的博客文章:
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
请参阅第三节“提取表单对象”。
问题是,是的,你绝对应该将业务逻辑从控制器中移出。但它也不属于您的数据模型(Activerecord模型)。您将需要使用数字2,“提取服务对象”和3“提取表单对象”的组合,以便为您的应用程序构建一个良好的结构。
您还可以观看布莱恩在这里解释这些概念的精彩视频:http://www.youtube.com/watch?v=5yX6ADjyqyE
有关这些主题的更多信息,强烈建议您观看Confreaks会议视频:http://www.confreaks.com/