Статья Кирилла Мокевнина ссылка хабр
Поместил сюда дабы не потерять.
В большинстве встречавшихся мне rails проектов, структура контроллеров не имеет никакой организации и проект растет как придется. В больших проектах это приводит к тому что контроллеры становятся огромными (с десятками actions), а условные фильтры растягиваются на весь экран. Разобраться в таком коде бывает очень не просто.
Поработав с большим количеством rails проектов, у меня был сформирован подход к организации иерархии контроллеров, которая позволила унифицировать их структуру и упростить код.
В первую очередь удобно на верхнем уровне расположить основные неймспейсы проекта, такие как web, mobile, api, feeds и другие. При этом модуль admin будет находиться в web.
Внутри апи можно так же выделить подмодули, разделенные по версии, например, v1, v2. Либо делать папки на верхнем уровне api1, api2 и т.п. тыц
Я использую такую структуру даже в самых простейших случаях. Практика показывает, что любая из этих составляющих легко может добавиться тогда, когда этого никто не планировал.
Для каждого модуля создается Namespace::ApplicationController
для того чтобы не смешивать логику подходящую для конкретных неймспейсов.
Но в роутинге будут небольшие отличия для разных модулей. Web задается как scope. В результате мы получим хелперы без лишних префиксов, а вот для остальных используется namespace.
Начну сразу с примера: профайл пользователя на сайте. Он может быть доступен по такому урлу /users/:id
и его контроллер UsersController
. Список постов пользователя доступен по /users/:id/posts
. Соответственно мы имеем PostsController с index внутри которого получаем посты по юзеру. Затем у нас появляется задача выводить все посты одним списком. Мы используем тот же контроллер posts и добавляем туда метод index_all. При этом юзер тут уже не нужен. В конце концов, добавляя множество представлений, появится часть методов которые выводят эти посты (и обрабатывают их) для юзера, а часть без него. Дальше появятся фильтры, например, before_filter :load_user, :only => [:index …]
, и так до тех пор, пока не останется людей способных понимать что тут происходит.
Тоже самое повторится для вывода комментариев, лайков и многого другого. При этом вывод постов может быть не только по пользователю, но и по категории и по многим другим связям в которых посты будут вложенными ресурсами.
В данном случае так же будет разумным создать модули для соответствующих ресурсов.
И для каждого неймспейса будет собственный ApplicationController содержащий общие методы модуля.
Для всех контроллеров находящихся в этом модуле будет доступен current_section без фильтров.
Профит:
Контроллеры становятся меньше и проще. Глядя на структуру контроллеров уже много можно сказать о том как организован проект и чего можно ожидать в конкретном контроллере. Уходит много условных фильтров. Упрощается тестирование; Единый организационный подход;
Так же, для удобства и единообразия, входные контроллеры неймспейса обзываются welcome. И подключаются в роутах через root :to => ‘welcome#index’
внутри соответствующего scope.
Реальный пример:
Рассмотрим как организовать контроллеры на реальном примере. Допустим потребовалось сделать интеграцию с facebook.
При подходе в лоб, был бы сделан какой нибудь контроллер FacebookController, в который помещались бы все методы для работы с фейсбуком. В итоге мы бы получили часть методов, которая должна работать только из аккаунта пользователя, а часть только для тех кто не авторизован и т.п.
Подход с разделением:
{% highlight ruby linenos %} resource :facebook, :only => [:new, :create] {% endhighlight %}
В данном случае о пользователе еще ничего не известно, а вот при изменении интеграции (например настройка автошаринга) или удалении мы работаем с конкретным пользователем. Это можно реализовать, например, так:
{% highlight ruby linenos %} # app/controllers/web/account/facebook_controller.rb resource :account do scope :module => :account do resource :facebook, :only => [:edit, :update, :destroy] end end {% endhighlight %}
А если понадобится дать возможность интегрироваться уже авторизованному юзеру? Тогда в последний пример добавляем :new, :create.
В итоге вместо одного контроллера мы получили два (но может быть и больше если задача того потребует), но при этом все методы сгруппированы по вариантам использования.