OO Design in Rails: Куди покласти речі


244

Мені дуже подобається Rails (хоча я, як правило, безрезультатний), і мені подобається, що Ruby дуже OO. Тим не менш, тенденція до створення величезних підкласів 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'oh. Додавання окремого каталогу для немоделей мені не прийшло в голову. Я можу відчути охайність, що настає ...
Майк Вудхаус

Єгуда, дякую за це. Чудова відповідь. Це саме те, що я бачу в програмах, які я успадковую (і в тих, які я роблю): все в контролерах, моделях, видах і помічниках, автоматично передбачених контролерами та видами. Потім приходять міксини з lib, але ніколи не буває спроб зробити справжнє OO моделювання. Ти прав, правда: у "додатках / класах чи деінде". Просто хотів перевірити, чи не вистачає якоїсь стандартної відповіді ...
Dan Rosenstark

33
З більш новими версіями config.autoload_paths за замовчуванням застосовується до всіх каталогів у додатку. Тому вам не потрібно змінювати config.load_paths, як описано вище. Я не впевнений, що все-таки є eager_load_paths (все ще), і мені потрібно це вивчити. Хтось уже знає?
Шям Хабаракада

Пасивний агресивний по відношенню до Intermediates: P
Себастьян Паттен

8
Було б добре, якби Rails постачався з цією папкою "класи", щоб заохотити "принцип єдиної відповідальності" та дати можливість розробникам створювати об'єкти, на яких не підтримується база даних. Реалізація "Проблеми" в Rails 4 (див. Відповідь Сімона), схоже, взяла участь у впровадженні модулів для обміну логікою між моделями. Однак, для простих класів Ruby, які не підтримуються базами даних, не було створено такого інструменту. З огляду на те, що Rails дуже впевнено, мені цікаво, що думка про те, щоб НЕ включати таку папку?
Райан Френсіс

62

Оновлення : використання концернів було підтверджено як новий за замовчуванням у Rails 4 .

Це дійсно залежить від характеру самого модуля. Я зазвичай розміщую розширення контролера / моделі в папці / issues в програмі.

# 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

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

Довідкові файли зазвичай містять допоміжні методи, а іноді і класи, коли об’єкт призначений для використання помічниками (наприклад, Builders форм).

Це дійсно загальний огляд. Надайте більше деталей щодо конкретних прикладів, якщо ви хочете отримати більше індивідуальних пропозицій. :)


Химерна річ. Я не можу отримати цю вимогу RAE_S_ROOT + "/ lib / my_module" для роботи з чимось із каталогу lib. Він безумовно виконує і скаржиться, якщо файл не знайдений, але він не перезавантажує його.
Dan Rosenstark

Рубі вимагають лише один раз завантажувати речі. Якщо ви хочете щось завантажити беззастережно, використовуйте load.
Чак

Крім того, мені здається досить незвичним, що ви хочете два рази завантажити файл протягом життя примірника програми. Ви генеруєте код під час руху?
Чак

Чому ви використовуєте requ_dependency замість вимагати? Також зауважте, що якщо ви дотримуєтесь конвенцій про іменування, то їх взагалі не потрібно використовувати. Якщо ви створюєте MyModule в lib / my_module, ви можете викликати MyModule без попередньої потреби (навіть якщо використання вимагає швидшого, а іноді і більш зрозумілого). Також зауважте, що файл в / lib завантажується лише один раз на завантажувальному інструменті.
Сімоне Карлетті

1
Використання побоювань стосується
bbozo

10

... тенденція до створення величезних підкласів ActiveRecord та величезних контролерів цілком природна ...

"величезний" - це тривожне слово ... ;-)

Як ваші контролери стають величезними? Це те, на що слід звернути увагу: в ідеалі контролери повинні бути тонкими. Вибираючи велике правило з повітря, я б радив, що якщо ви регулярно маєте більше, ніж, скажімо, 5 або 6 рядків коду на метод контролера (дії), то, можливо, ваші контролери занадто жирні. Чи є дублювання, яке може перейти у функцію помічника чи фільтр? Чи є бізнес-логіка, яку можна підштовхнути до моделей?

Як ваші моделі можуть бути величезними? Чи слід шукати способи зменшити кількість обов'язків у кожному класі? Чи є якісь звичні способи поведінки, які ви можете витягти в міксини? Або області функціональності, які ви можете делегувати допоміжним класам?

EDIT: Намагаємось трохи розширити, сподіваємось, що не спотворить нічого надто поганого ...

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

Фільтри: Скажіть, що у вас є один і той же рядок коду в декількох діях (досить часто - пошук об'єкта з використанням params[:id]подібних). Це дублювання можна абстрагувати спочатку окремим методом, а потім повністю вийти з дій, оголосивши фільтр у визначенні класу, наприклад before_filter :get_object. Дивіться Розділ 6 у посібнику по рейкових трасах ActionController. Нехай декларативне програмування стане вашим другом.

Моделі рефакторингу - це трохи більше релігійна річ. Учні дядька Боба запропонують, наприклад, дотримуватися П’яти заповідей СОЛІДА . Джоел та Джефф можуть порекомендувати більше, але, «прагматичний» підхід, хоча згодом вони виявилися трохи примиреними . Пошук одного або декількох методів у класі, які працюють на чітко визначеному підмножині його атрибутів, - це один із способів спробувати визначити класи, які можуть бути відновлені з вашої моделі, похідної від ActiveRecord.

До речі, моделі рейок не повинні бути підкласами ActiveRecord :: Base. Або кажучи інакше, модель не повинна бути аналогом таблиці або навіть пов’язана з будь-чим, що взагалі зберігається. Ще краще, якщо ви називаєте свій файл app/modelsзгідно з умовами Rails (зателефонуйте #underscore на ім'я класу, щоб дізнатися, що Rails буде шукати), Rails знайде його без requireнеобхідності.


Щоправда, з усіх пунктів, Майк, і дякую за вашу турботу ... Я успадкував проект, в якому було кілька способів на контролерах, які були величезні. Я розбив їх на більш дрібні методи, але сам контролер все ще "жирний". Тож, що я шукаю, - це всі мої варіанти, щоб вивантажити речі. Ваші відповіді: "допоміжні функції", "фільтри", "моделі", "міксин" та "допоміжні класи". Тож, куди я можу помістити ці речі? Чи можу я організувати ієрархію класів, яка автоматично завантажується в програму dev env?
Dan Rosenstark

1

Ось чудовий пост у блозі про рефакторинг жирових моделей, які, здається, виникають із філософії "тонкого контролера":

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

Основне повідомлення - "Не витягувати міксини з жирних моделей", використовуйте натомість сервісні класи, автор надає 7 моделей для цього

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.