1

I've come to appreciate the "skinny controllers" philosophy in Rails that says that business logic should not be in controllers, but that they should basically only be responsible for calling a few model methods and then deciding what to render/redirect. Pushing business logic into the model (or elsewhere) keeps action methods clean (and avoids stubbing long chains of ActiveRecord methods in functional tests of controllers).

Most cases I've run across are like this: I have three models, Foo, Bar, and Baz. Each of them has a method or scope defined (call it filter) that narrows down the objects to what I'm looking for. A skinny action method might look like:

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

However, I've run into a case where the view needs to display a more hierarchical data structure. For example, Foo has_many bars and Bar has_many bazs. In the view (a general "dashboard" page), I'm going to display something like this, where each foo, bar, and baz has been filtered down with some criteria (e.g. for each level I only want to show active ones):

Foo1 - Bar1 (Baz1, Baz2)
       Bar2 (Baz3, Baz4)
-----------------------
Foo2 - Bar3 (Baz5, Baz6)
       Bar4 (Baz7, Baz8)

To provide the view with the data it needs, my initial thought is to put something crazy like this in the controller:

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

I could push that down to the Foo model, but that's not much better. This doesn't seem like a complex data structure that merits factoring out into a separate non-ActiveRecord model or something like that, it's just fetching some foos and their bars and their bazs with a very simple filter applied at each step.

What is the best practice for passing hierarchical data like this from a controller to a view?

4

2 回答 2

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 %>

控制器中的任何类型的哈希构建都是不必要的。您在视图中使用的抽象级别是合理的。

于 2013-10-29T03:17:07.497 回答
1

如果Extracting a Form object,这是此类事情被广泛接受的最佳实践之一。Code Climate 的 Bryan Helmkamp 写了一篇非常好的博客文章:

http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/

请参阅第三节“提取表单对象”。

问题是,是的,您绝对应该将业务逻辑移出控制器。但它也不属于您的数据模型(Activerecord 模型)。您将需要使用 2 号“提取服务对象”和 3 号“提取表单对象”的组合,以便为您的应用构建良好的结构。

您还可以在此处观看 Bryan 解释这些概念的精彩视频:http ://www.youtube.com/watch?v=5yX6ADjyqyE

有关这些主题的更多信息,强烈建议您观看 Confreaks 会议视频: http: //www.confreaks.com/

于 2013-11-01T11:13:52.887 回答