Skinny控制器传递分层/嵌套数据以进行查看

时间:2013-10-26 01:36:15

标签: ruby-on-rails data-structures rails-activerecord separation-of-concerns

我开始欣赏Rails中的“瘦调控制器”哲学,它认为业务逻辑不应该在控制器中,但它们应该基本上只负责调用一些模型方法,然后决定渲染/重定向的内容。将业务逻辑推入模型(或其他地方)可以保持操作方法的清洁(并避免在控制器的功能测试中存根长链的ActiveRecord方法)。

我碰到的大多数情况都是这样的:我有三个模型,FooBarBaz。他们每个人都有一个定义的方法或范围(称之为filter),将对象缩小到我正在寻找的范围。一个瘦的动作方法可能如下所示:

def index
  @foos = Foo.filter
  @bars = Bar.filter
  @bazs = Baz.filter
end

但是,我遇到了一个视图需要显示更多层次结构的数据结构的情况。例如,Foo has_many barsBar has_many bazs。在视图(一般的“信息中心”页面)中,我将显示类似的内容,其中每个foobarbaz都已按照一些标准进行过滤(例如,对于每个级别,我只想显示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与每个步骤都应用一个非常简单的过滤器。

将这样的分层数据从控制器传递到视图的最佳做法是什么?

2 个答案:

答案 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/