Pull to refresh

Иерархия контроллеров

Ruby on Rails *
В большинстве встречавшихся мне rails проектов, структура контроллеров не имеет никакой организации и проект растет как придется. В больших проектах это приводит к тому что контроллеры становятся огромными (с десятками actions), а условные фильтры растягиваются на весь экран. Разобраться в таком коде бывает очень не просто.

Поработав с большим количеством rails проектов, у меня был сформирован подход к организации иерархии контроллеров, которая позволила унифицировать их структуру и упростить код.



Верхний уровень



В первую очередь удобно на верхнем уровне расположить основные неймспейсы проекта, такие как web, mobile, api, feeds и другие. При этом модуль admin будет находиться в web.



Внутри апи можно так же выделить подмодули, разделенные по версии, например, v1, v2. Либо делать папки на верхнем уровне api1, api2 и т.п.
link: freelancing-gods.com/posts/versioning_your_ap_is

Я использую такую структуру даже в самых простейших случаях. Практика показывает, что любая из этих составляющих легко может добавиться тогда, когда этого никто не планировал.

Для каждого модуля создается 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, в который помещались бы все методы для работы с фейсбуком. В итоге мы бы получили часть методов, которая должна работать только из аккаунта пользователя, а часть только для тех кто не авторизован и т.п.

Подход с разделением:

  resource :facebook, :only => [:new, :create]


В данном случае о пользователе еще ничего не известно, а вот при изменении интеграции (например настройка автошаринга) или удалении мы работаем с конкретным пользователем. Это можно реализовать, например, так:

# app/controllers/web/account/facebook_controller.rb
resource :account do
  scope :module => :account do
    resource :facebook, :only => [:edit, :update, :destroy]
  end
end


А если понадобится дать возможность интегрироваться уже авторизованному юзеру? Тогда в последний пример добавляем :new, :create.

В итоге вместо одного контроллера мы получили два (но может быть и больше если задача того потребует), но при этом все методы сгруппированы по вариантам использования.
Tags:
Hubs:
Total votes 41: ↑38 and ↓3 +35
Views 11K
Comments Comments 29