ОО Дизайн в Rails: куда положить вещи
Я действительно наслаждаюсь Rails (хотя я вообще без RESTless), и мне нравится, что Ruby очень хорош. Тем не менее, тенденция создавать огромные подклассы ActiveRecord и огромные контроллеры вполне естественна (даже если вы используете контроллер для каждого ресурса). Если бы вы создавали более глубокие объектные миры, куда бы вы поместили классы (и модули, я полагаю)? Я спрашиваю о представлениях (в самих помощниках?), Контроллерах и моделях.
С Lib хорошо, и я нашел несколько решений для его перезагрузки в среде разработчиков, но я хотел бы знать, есть ли лучший способ сделать это. Я действительно просто обеспокоен тем, что классы становятся слишком большими. Кроме того, как насчет двигателей и как они вписываются?
4 ответа
Поскольку 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 и выше.
Обновление: использование проблем было подтверждено как новое значение по умолчанию в 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).
Это действительно общий обзор. Пожалуйста, предоставьте более подробную информацию о конкретных примерах, если вы хотите получить больше индивидуальных предложений.:)
... тенденция создавать огромные подклассы ActiveRecord и огромные контроллеры вполне естественна...
"огромный" это тревожное слово...;-)
Как ваши контроллеры становятся огромными? Это то, на что вы должны смотреть: в идеале контроллеры должны быть тонкими. Выбирая эмпирическое правило, я бы предложил, что если у вас регулярно есть, скажем, 5 или 6 строк кода на метод (действие) контроллера, то ваши контроллеры, вероятно, слишком толстые. Есть ли дублирование, которое может перейти в вспомогательную функцию или фильтр? Есть ли бизнес-логика, которая могла бы быть внедрена в модели?
Как ваши модели становятся огромными? Стоит ли искать способы уменьшить количество обязанностей в каждом классе? Есть ли какие-либо общие поведения, которые вы можете извлечь в mixins? Или области функциональности, которые вы можете делегировать вспомогательным классам?
РЕДАКТИРОВАТЬ: Попытка немного расширить, надеюсь, не слишком сильно искажая...
Помощники: живут в app/helpers
и в основном используются для упрощения просмотра. Они либо для конкретного контроллера (также доступны для всех представлений для этого контроллера), либо обычно доступны (module ApplicationHelper
в application_helper.rb).
Фильтры: скажем, у вас одна и та же строка кода в нескольких действиях (довольно часто поиск объекта с использованием params[:id]
или похожие). Это дублирование может быть абстрагировано сначала для отдельного метода, а затем полностью исключено из действия путем объявления фильтра в определении класса, такого как before_filter :get_object
, См. Раздел 6 Руководства по рельсам ActionController. Пусть декларативное программирование станет вашим другом.
Рефакторинг моделей - это скорее религиозная вещь. Ученики дяди Боба предложат, например, что вы будете следовать пяти заповедям SOLID. Джоэл и Джефф могут порекомендовать более "прагматичный" подход, хотя впоследствии они казались немного более согласованными. Поиск одного или нескольких методов в классе, которые работают с четко определенным подмножеством его атрибутов, является одним из способов определения классов, которые могут быть реорганизованы из вашей модели, производной от ActiveRecord.
Кстати, модели Rails не обязательно должны быть подклассами ActiveRecord::Base. Или, другими словами, модель не должна быть аналогом таблицы или даже иметь отношение к чему-либо, что хранится вообще. Еще лучше, пока вы называете свой файл в app/models
согласно соглашениям Rails (вызовите #underscore в имени класса, чтобы узнать, что будет искать Rails), Rails найдет его без каких-либо require
Это необходимо.
Вот отличное сообщение в блоге о рефакторинге толстых моделей, которые, похоже, возникают из философии "тонкого контроллера":
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/
Основное сообщение: "Не извлекать миксины из жировых моделей", вместо этого используйте классы обслуживания, автор предоставляет для этого 7 шаблонов.