Тощий контроллер передает иерархические/вложенные данные для просмотра

Я пришел к пониманию философии "тощих контроллеров" в 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 с очень простым фильтром, применяемым на каждом шаге.

Какова наилучшая практика для передачи иерархических данных, подобных этому, с контроллера на представление?

2 ответа

Вы можете получить @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 %>

Любые конструкции хэша в контроллере не нужны. Уровень абстракции, с которым вы работаете в представлении, является разумным.


Одна из общепринятых лучших практик для такого рода вещей, если вы извлекаете объект Form. Брайан Хелмкамп из Code Climate написал очень хорошее сообщение в блоге об этом:

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

См. Раздел 3 "Извлечь объекты формы".

Дело в том, что да, вы должны перенести бизнес-логику из своих контроллеров. Но это не относится к вашим моделям данных (модели Activerecord). Вы захотите использовать комбинацию чисел 2, "Извлечь сервисные объекты" и 3 "Извлечь объекты формы", чтобы создать хорошую структуру для вашего приложения.

Вы также можете посмотреть хорошее видео Брайана, объясняющего эти понятия здесь: http://www.youtube.com/watch?v=5yX6ADjyqyE

Для получения дополнительной информации по этим темам также настоятельно рекомендуется смотреть видеоконференции Confers: http://www.confreaks.com/

licensed under cc by-sa 3.0 with attribution.