ОО Дизайн в Rails: куда положить вещи

218

Мне очень нравится Rails (хотя я вообще RESTless), и мне нравится, что Ruby очень OO. Тем не менее, тенденция создания огромных подклассов ActiveRecord и огромных контроллеров вполне естественна (даже если вы используете контроллер на ресурс). Если бы вы создали более глубокие объектные миры, где бы вы поместили классы (и модули, я полагаю)? Я спрашиваю о просмотрах (в самих помощниках?), Контроллерах и моделях.

Lib в порядке, и я нашел некоторые решения, чтобы заставить его перезагрузить в среде dev, но я хотел бы знать если есть лучший способ сделать это. Меня действительно беспокоит слишком большое количество классов. Кроме того, как насчет двигателей и как они вписываются?

Теги:
directory-structure

4 ответа

381
Лучший ответ

Поскольку Rails предоставляет структуру с точки зрения MVC, естественно использовать только контейнеры модели, представления и контроллера, которые предоставляются вам. Типичная идиома для новичков (и даже некоторых промежуточных программистов) заключается в том, чтобы вставить всю логику в приложение в модель (класс базы данных), контроллер или представление.

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

Тощие контроллеры - это, по сути, хорошая идея, но следствие - помещение всего в модель, на самом деле не лучший план.

В Ruby у вас есть несколько хороших вариантов для того, чтобы сделать вещи более модульными. Довольно популярным является просто использование модулей (обычно спрятанных в lib), которые содержат группы методов, а затем включают модули в соответствующие классы. Это помогает в случаях, когда у вас есть категории функциональных возможностей, которые вы хотите использовать повторно в нескольких классах, но где функциональность по-прежнему привязана к классам.

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

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

Хороший способ подумать об этом - это принцип "единой ответственности", в котором говорится, что класс должен нести ответственность за одно (или небольшое количество) вещей. Ваши модели несут ответственность за сохранение данных из вашего приложения в базе данных. Ваши контроллеры несут ответственность за получение запроса и возвращение жизнеспособного ответа.

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

config.load_paths << File.join(Rails.root, "app", "classes")

Если вы пользуетесь пассажиром или JRuby, вы, вероятно, также хотите добавить свой путь к загруженным путям:

config.eager_load_paths << File.join(Rails.root, "app", "classes")

Суть в том, что, как только вы дойдете до точки в Rails, где вы задаете этот вопрос, пришло время поднять ваши рубиновые отбивные и начать моделировать классы, которые не являются только классами MVC, которые Rails дает вам по умолчанию.

Обновление: Этот ответ относится к Rails 2.x и выше.

  • 0
    D'о. Добавление отдельного каталога для не-моделей мне не приходило в голову. Я чувствую приближение ...
  • 0
    Иегуда, спасибо за это. Отличный ответ. Это именно то, что я вижу в приложениях, которые я наследую (и те, которые я создаю): все в контроллерах, моделях, представлениях и помощниках, автоматически предоставляемых для контроллеров и представлений. Затем идут миксины из lib, но никогда не делается попытка реального ОО моделирования. Но вы правы: в «приложениях / классах или где-то еще». Просто хотел проверить, есть ли какой-то стандартный ответ, который я пропускаю ...
Показать ещё 8 комментариев
60

Обновление. Использование Концернов было подтверждено как новый по умолчанию в Rails 4.

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

# concerns/authentication.rb
module Authentication
  ...
end    

# controllers/application_controller.rb
class ApplicationController
  include Authentication
end



# concerns/configurable.rb
module Configurable
  ...
end    

class Model 
  include Indexable
end 

# controllers/foo_controller.rb
class FooController < ApplicationController
  include Indexable
end

# controllers/bar_controller.rb
class BarController < ApplicationController
  include Indexable
end

/lib - мой предпочтительный выбор для библиотек общего назначения. У меня всегда есть пространство имен проектов в lib, где я помещаю все библиотеки, специфичные для приложения.

/lib/myapp.rb
module MyApp
  VERSION = ...
end

/lib/myapp/CacheKey.rb
/lib/myapp/somecustomlib.rb

Расширения ядра Ruby/Rails обычно выполняются в инициализаторах конфигурации, поэтому библиотеки загружаются только один раз на Rails-расширителе.

/config/initializer/config.rb
/config/initializer/core_ext/string.rb
/config/initializer/core_ext/array.rb

Для многократно используемых фрагментов кода я часто создаю (микро) плагины, чтобы я мог повторно использовать их в других проектах.

Файлы-помощники обычно содержат вспомогательные методы, а иногда и классы, когда объект предназначен для использования помощниками (например, Form Builders).

Это действительно общий обзор. Пожалуйста, предоставьте более подробную информацию о конкретных примерах, если вы хотите получить более индивидуальные предложения.:)

  • 0
    Странная вещь. Я не могу заставить этот require_dependency RAILS_ROOT + "/ lib / my_module" работать с чем-то из каталога lib. Он определенно выполняется и выдает сообщение, если файл не найден, но не перезагружает его.
  • 0
    Ruby требует загружать вещи только один раз. Если вы хотите загрузить что-то безоговорочно, используйте load.
Показать ещё 7 комментариев
10

... тенденция к огромному Подклассы ActiveRecord и огромные контроллеры вполне естественны...

"огромный" - это тревожное слово...; -)

Как ваши контроллеры становятся огромными? Это то, на что вы должны обратить внимание: в идеале контроллеры должны быть тонкими. Выбирая правильное правило из воздуха, я бы предположил, что если вы регулярно используете более, чем, скажем, 5 или 6 строк кода для каждого метода (действия) контроллера, то ваши контроллеры, вероятно, слишком толстые. Существует ли дублирование, которое может переместиться в вспомогательную функцию или фильтр? Есть ли бизнес-логика, которая может быть перенесена в модели?

Как ваши модели становятся огромными? Должны ли вы смотреть на способы сокращения числа обязанностей в каждом классе? Есть ли общее поведение, которое вы можете извлечь в миксины? Или области функциональности, которые вы можете делегировать вспомогательным классам?

РЕДАКТИРОВАТЬ: Попытка немного расширить, надеюсь, не слишком сильно искажает...

Помощники: живут в app/helpers и в основном используются, чтобы упростить просмотр. Они либо специфичны для контроллера (также доступны для всех представлений для этого контроллера), либо вообще доступны (module ApplicationHelper в application_helper.rb).

Фильтры: скажем, что у вас есть одна и та же строка кода в нескольких действиях (нередко, поиск объекта с помощью params[:id] или аналогичного). Это дублирование может быть сначала абстрагировано отдельным методом, а затем полностью исключено из действий, объявив фильтр в определении класса, например before_filter :get_object. См. Раздел 6 в ActionController Rails Guide Пусть декларативное программирование будет вашим другом.

Рефакторинг - это немного более религиозная вещь. Ученики Дядя Боб предложит, например, следовать пяти заповедям SOLID. Джоэл и Джефф могут порекомендовать более прагматичный подход, хотя они выглядели как немного более примиренные впоследствии. Поиск одного или нескольких методов в классе, которые работают с четко определенным подмножеством его атрибутов, - это один из способов попробовать идентифицировать классы, которые могут быть реорганизованы из вашей модели, основанной на ActiveRecord.

Модели Rails необязательно должны быть подклассами ActiveRecord:: Base, между прочим. Или, говоря иначе, модель не обязательно должна быть аналогом таблицы или даже связана с чем-либо, что угодно. Еще лучше, если вы назовете свой файл в app/models в соответствии с соглашениями Rails (вызовите #underscore на имя класса, чтобы узнать, какие Rails будут искать), Rails найдет его без необходимости require.

  • 0
    Правда по всем параметрам, Майк, и спасибо за вашу заботу ... Я унаследовал проект, в котором было несколько методов на контроллерах, которые были огромными. Я разбил их на более мелкие методы, но сам контроллер все еще "толстый". Так что я ищу все мои варианты разгрузки вещей. Ваши ответы: «вспомогательные функции», «фильтры», «модели», «mixins» и «вспомогательные классы». Итак, где я могу положить эти вещи? Могу ли я организовать иерархию классов, которая автоматически загружается в dev env?
1

Здесь отличное сообщение в блоге о рефакторинге толстых моделей, которые, похоже, возникают из "тонкого контроллера":

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

Основное сообщение: "Dont Extract Mixins from Fat Models", вместо этого используйте классы обслуживания, автор предоставляет 7 шаблонов для этого

Ещё вопросы

Сообщество Overcoder
Наверх
Меню