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

244

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

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

Дэн Розенстарк
источник

Ответы:

384

Поскольку 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 и выше.

Иегуда Кац
источник
D'о. Добавление отдельного каталога для не-моделей мне не приходило в голову. Я чувствую приближение ...
Майк Вудхаус
Иегуда, спасибо за это. Отличный ответ. Это именно то, что я вижу в приложениях, которые я наследую (и те, которые я создаю): все в контроллерах, моделях, представлениях и помощниках, автоматически предоставляемых для контроллеров и представлений. Затем идут миксины из lib, но никогда не делается попытка реального ОО моделирования. Однако вы правы: в «приложениях / классах или где-либо еще». Просто хотел проверить, есть ли какой-то стандартный ответ, который я пропускаю ...
Дэн Розенстарк,
33
В более поздних версиях config.autoload_paths по умолчанию используется для всех каталогов в приложении. Поэтому вам не нужно изменять config.load_paths, как описано выше. Я не уверен насчет eager_load_paths (пока), и мне нужно изучить это. Кто-нибудь уже знает?
Шьям Хабаракада
Пассивно агрессивно по отношению к промежуточным звенам: P
Себастьян Паттен
8
Было бы неплохо, если бы Rails поставлялся с этой папкой «classes», чтобы поощрять «принцип единой ответственности» и позволять разработчикам создавать объекты, не поддерживаемые базой данных. Реализация «Заботы» в Rails 4 (см. Ответ Симоне), похоже, позаботилась о реализации модулей для совместного использования логики между моделями. Тем не менее, такой инструмент не был создан для простых классов Ruby, которые не поддерживаются базой данных. Учитывая, что Rails очень самоуверенный, мне любопытно мыслить процесс НЕ включая такую ​​папку?
Райан Фрэнсис
62

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

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

# 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 обычно имеют место в инициализаторах конфигурации, так что библиотеки загружаются только один раз в boostrap Rails.

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

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

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

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

Симона Карлетти
источник
Странная вещь. Я не могу заставить этот require_dependency RAILS_ROOT + "/ lib / my_module" работать с чем-то из каталога lib. Он определенно выполняется и выдает сообщение, если файл не найден, но не перезагружает его.
Дэн Розенстарк
Ruby требует загружать вещи только один раз. Если вы хотите загрузить что-то безоговорочно, используйте load.
Чак
Кроме того, мне кажется довольно необычным, что вы захотите загрузить файл дважды за время существования экземпляра приложения. Вы генерируете код по ходу дела?
Чак
Почему вы используете require_dependency вместо require? Также обратите внимание, что если вы следуете соглашениям об именах, вам вообще не нужно использовать require. Если вы создаете MyModule в lib / my_module, вы можете вызывать MyModule без предварительного требования (даже если использование require должно быть быстрее и иногда более читабельным). Также обратите внимание, что файл в / lib загружается только один раз при загрузке.
Симона Карлетти
1
Использование проблем касается
bbozo
10

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

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

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

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

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

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

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

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

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

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

Вот отличное сообщение в блоге о рефакторинге толстых моделей, которые, похоже, возникли из философии «тонкого контроллера»:

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

Основное сообщение: «Не извлекать миксины из жировых моделей», вместо этого используйте классы обслуживания, для этого автор предлагает 7 шаблонов.

bbozo
источник