OO Design in Rails: Wo man Sachen hinstellt


244

Ich genieße Rails wirklich (obwohl ich im Allgemeinen RESTless bin) und ich genieße es, dass Ruby sehr OO ist. Die Tendenz, große ActiveRecord-Unterklassen und große Controller zu erstellen, ist jedoch ganz natürlich (selbst wenn Sie einen Controller pro Ressource verwenden). Wenn Sie tiefere Objektwelten erschaffen würden, wo würden Sie die Klassen (und Module, nehme ich an) platzieren? Ich frage nach Ansichten (in den Helfern selbst?), Controllern und Modellen.

Lib ist in Ordnung, und ich habe einige Lösungen gefunden, um es in einer Entwicklungsumgebung neu zu laden , aber ich würde gerne wissen, ob es einen besseren Weg gibt, dies zu tun. Ich mache mir wirklich nur Sorgen, dass die Klassen zu groß werden. Und was ist mit Motoren und wie passen sie zusammen?

Antworten:


384

Da Rails eine Struktur in Bezug auf MVC bietet, werden natürlich nur die für Sie bereitgestellten Modell-, Ansichts- und Controller-Container verwendet. Die typische Redewendung für Anfänger (und sogar einige fortgeschrittene Programmierer) besteht darin, die gesamte Logik in der App in das Modell (Datenbankklasse), den Controller oder die Ansicht zu packen.

Irgendwann weist jemand auf das Paradigma "Fettmodell, Skinny-Controller" hin, und fortgeschrittene Entwickler entfernen hastig alles von ihren Controllern und werfen es in das Modell, das zu einem neuen Mülleimer für die Anwendungslogik wird.

Dünne Controller sind in der Tat eine gute Idee, aber die Konsequenz - alles in das Modell zu integrieren - ist nicht wirklich der beste Plan.

In Ruby haben Sie einige gute Möglichkeiten, die Dinge modularer zu gestalten. Eine ziemlich beliebte Antwort ist, nur Module zu verwenden (normalerweise versteckt lib), die Gruppen von Methoden enthalten, und die Module dann in die entsprechenden Klassen aufzunehmen. Dies ist hilfreich, wenn Sie über Funktionskategorien verfügen, die Sie in mehreren Klassen wiederverwenden möchten, die Funktionalität jedoch noch fiktiv an die Klassen gebunden ist.

Denken Sie daran, wenn Sie ein Modul in eine Klasse aufnehmen, werden die Methoden zu Instanzmethoden der Klasse, sodass Sie immer noch eine Klasse mit einer Vielzahl von Methoden erhalten. Sie sind nur gut in mehrere Dateien organisiert.

Diese Lösung kann in einigen Fällen gut funktionieren. In anderen Fällen sollten Sie darüber nachdenken, Klassen in Ihrem Code zu verwenden, die keine Modelle, Ansichten oder Controller sind.

Eine gute Möglichkeit, darüber nachzudenken, ist das "Prinzip der Einzelverantwortung", das besagt, dass eine Klasse für eine einzelne (oder eine kleine Anzahl) von Dingen verantwortlich sein sollte. Ihre Modelle sind dafür verantwortlich, dass Daten aus Ihrer Anwendung in der Datenbank gespeichert werden. Ihre Controller sind dafür verantwortlich, eine Anfrage zu erhalten und eine brauchbare Antwort zurückzugeben.

Wenn Sie Konzepte haben, die nicht genau in diese Felder passen (Persistenz, Anforderungs- / Antwortverwaltung), möchten Sie wahrscheinlich darüber nachdenken, wie Sie die betreffende Idee modellieren würden . Sie können Nichtmodellklassen in App / Klassen oder an einer anderen Stelle speichern und dieses Verzeichnis Ihrem Ladepfad hinzufügen, indem Sie Folgendes tun:

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

Wenn Sie Passagier oder JRuby verwenden, möchten Sie wahrscheinlich auch Ihren Pfad zu den eifrigen Ladepfaden hinzufügen:

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

Das Fazit ist, dass es an der Zeit ist, Ihre Ruby-Chops zu verbessern und Klassen zu modellieren, die nicht nur die MVC-Klassen sind, die Rails Ihnen standardmäßig zur Verfügung stellt, sobald Sie einen Punkt in Rails erreicht haben, an dem Sie diese Frage stellen.

Update: Diese Antwort gilt für Rails 2.x und höher.


D'oh. Das Hinzufügen eines separaten Verzeichnisses für Nicht-Models war mir nicht in den Sinn gekommen. Ich kann ein
Mike Woodhouse

Yehuda, danke dafür. Gute Antwort. Genau das sehe ich in den Apps, die ich erbe (und die ich mache): alles in Controllern, Modellen, Ansichten und den Hilfsprogrammen, die automatisch für Controller und Ansichten bereitgestellt werden. Dann kommen die Mixins von lib, aber es gibt nie einen Versuch, eine echte OO-Modellierung durchzuführen. Sie haben jedoch Recht: in "Apps / Klassen oder anderswo". Ich wollte nur überprüfen, ob es eine Standardantwort gibt, die mir fehlt ...
Dan Rosenstark

33
In neueren Versionen verwendet config.autoload_paths standardmäßig alle Verzeichnisse unter App. Sie müssen config.load_paths also nicht wie oben beschrieben ändern. Ich bin mir (noch) nicht sicher über eifrige_Ladungspfade und muss das untersuchen. Weiß jemand schon?
Shyam Habarakada

Passiv aggressiv gegenüber Intermediates: P
Sebastian Patten

8
Es wäre schön, wenn Rails mit diesem Ordner "classes" ausgeliefert würde, um das "Prinzip der Einzelverantwortung" zu fördern und Entwicklern das Erstellen von Objekten zu ermöglichen, die nicht datenbankgestützt sind. Die "Concerns" -Implementierung in Rails 4 (siehe Simones Antwort) scheint sich um die Implementierung von Modulen gekümmert zu haben, um die Logik modellübergreifend zu teilen. Es wurde jedoch kein solches Tool für einfache Ruby-Klassen erstellt, die nicht datenbankgestützt sind. Angesichts der Tatsache, dass Rails sehr eigensinnig ist, bin ich neugierig, welchen Denkprozess dahinter steckt, einen solchen Ordner NICHT einzuschließen?
Ryan Francis

62

Update : Die Verwendung von Bedenken wurde als neuer Standard in Rails 4 bestätigt .

Es hängt wirklich von der Art des Moduls selbst ab. Normalerweise platziere ich Controller- / Modellerweiterungen in einem Ordner unter der App.

# 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 ist meine bevorzugte Wahl für Allzweckbibliotheken. Ich habe immer einen Projektnamespace in lib, in dem ich alle anwendungsspezifischen Bibliotheken ablege.

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

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

Ruby / Rails-Kernerweiterungen finden normalerweise in Konfigurationsinitialisierern statt, sodass Bibliotheken nur einmal auf Rails Boostrap geladen werden.

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

Für wiederverwendbare Codefragmente erstelle ich häufig (Mikro-) Plugins, damit ich sie in anderen Projekten wiederverwenden kann.

Hilfsdateien enthalten normalerweise Hilfsmethoden und manchmal Klassen, wenn das Objekt von Helfern (z. B. Form Buildern) verwendet werden soll.

Dies ist eine wirklich allgemeine Übersicht. Bitte geben Sie weitere Details zu bestimmten Beispielen an, wenn Sie individuellere Vorschläge erhalten möchten. :) :)


Bizarre Sache. Ich kann diese require_dependency RAILS_ROOT + "/ lib / my_module" nicht dazu bringen, mit etwas aus dem lib-Verzeichnis zu arbeiten. Es wird definitiv ausgeführt und beschwert sich, wenn die Datei nicht gefunden wird, aber es wird nicht neu geladen.
Dan Rosenstark

Rubys verlangen, dass Dinge nur einmal geladen werden. Wenn Sie etwas bedingungslos laden möchten, verwenden Sie load.
Chuck

Außerdem erscheint es mir ziemlich ungewöhnlich, dass Sie eine Datei während der Lebensdauer einer App-Instanz zweimal laden möchten. Generieren Sie unterwegs Code?
Chuck

Warum verwenden Sie require_dependency anstelle von require? Beachten Sie auch, dass Sie, wenn Sie Namenskonventionen befolgen, überhaupt keine Anforderungen verwenden müssen. Wenn Sie MyModule in lib / my_module erstellen, können Sie MyModule ohne vorherige Anforderung aufrufen (auch wenn die Verwendung von require schneller und manchmal besser lesbar sein sollte). Beachten Sie auch, dass die Datei in / lib nur einmal auf dem Bootstrap geladen wird.
Simone Carletti

1
Die Verwendung von Bedenken betrifft
bbozo

10

... die Tendenz, große ActiveRecord-Unterklassen und große Controller zu erstellen, ist ganz natürlich ...

"riesig" ist ein besorgniserregendes Wort ... ;-)

Wie werden Ihre Controller riesig? Das sollten Sie sich ansehen: Im Idealfall sollten Controller dünn sein. Wenn Sie eine Faustregel aus dem Nichts auswählen, würde ich vorschlagen, dass Ihre Controller wahrscheinlich zu fett sind, wenn Sie regelmäßig mehr als beispielsweise 5 oder 6 Codezeilen pro Controller-Methode (Aktion) haben. Gibt es Duplikate, die in eine Hilfsfunktion oder einen Filter verschoben werden könnten? Gibt es eine Geschäftslogik, die in die Modelle hineingedrückt werden könnte?

Wie werden deine Modelle riesig? Sollten Sie nach Möglichkeiten suchen, um die Anzahl der Verantwortlichkeiten in jeder Klasse zu verringern? Gibt es allgemeine Verhaltensweisen, die Sie in Mixins extrahieren können? Oder Funktionsbereiche, die Sie an Hilfsklassen delegieren können?

EDIT: Ich versuche ein bisschen zu expandieren, hoffentlich nichts zu sehr zu verzerren ...

Helfer: leben in app/helpersund werden meistens verwendet, um die Ansicht zu vereinfachen. Sie sind entweder Controller-spezifisch (auch für alle Ansichten dieses Controllers verfügbar) oder allgemein verfügbar ( module ApplicationHelperin application_helper.rb).

Filter: Angenommen, Sie haben dieselbe Codezeile in mehreren Aktionen (häufig Abrufen eines Objekts mit params[:id]oder ähnlich). Diese Duplizierung kann zuerst zu einer separaten Methode und dann vollständig aus den Aktionen abstrahiert werden, indem ein Filter in der Klassendefinition deklariert wird, z before_filter :get_object. Siehe Abschnitt 6 im ActionController Rails-Handbuch. Lassen Sie die deklarative Programmierung Ihr Freund sein.

Das Refactoring von Modellen ist eher eine religiöse Sache. Die Schüler von Onkel Bob schlagen zum Beispiel vor, dass Sie die fünf Gebote von SOLID befolgen . Joel & Jeff empfehlen möglicherweise einen eher "pragmatischen" Ansatz, obwohl sie später etwas versöhnter zu sein schienen . Das Finden einer oder mehrerer Methoden innerhalb einer Klasse, die mit einer klar definierten Teilmenge ihrer Attribute arbeiten, ist eine Möglichkeit, Klassen zu identifizieren, die möglicherweise aus Ihrem von ActiveRecord abgeleiteten Modell überarbeitet wurden.

Rails-Modelle müssen übrigens keine Unterklassen von ActiveRecord :: Base sein. Oder anders ausgedrückt: Ein Modell muss kein Analogon einer Tabelle sein oder sich auf irgendetwas beziehen, das überhaupt gespeichert ist. Noch besser, solange Sie Ihre Datei app/modelsgemäß den Rails-Konventionen benennen (rufen Sie #underscore für den Klassennamen auf, um herauszufinden, wonach Rails suchen wird), findet Rails sie, ohne dass requires erforderlich ist.


In jeder Hinsicht wahr, Mike, und danke für Ihre Besorgnis ... Ich habe ein Projekt geerbt, in dem es einige Methoden für Controller gab, die riesig waren. Ich habe diese in kleinere Methoden unterteilt, aber der Controller selbst ist immer noch "fett". Was ich also suche, sind alle meine Optionen, um Sachen auszulagern. Ihre Antworten lauten "Hilfsfunktionen", "Filter", "Modelle", "Mixins" und "Hilfsklassen". Wo kann ich diese Dinge dann hinstellen? Kann ich eine Klassenhierarchie organisieren, die in einer Entwicklungsumgebung automatisch geladen wird?
Dan Rosenstark

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.