2009-07-01 7 views
234

Ich genieße wirklich Rails (obwohl ich generell RESTless bin), und ich mag Ruby sehr OO. Dennoch ist die Tendenz, riesige ActiveRecord-Unterklassen und große Controller zu erstellen, ziemlich natürlich (selbst wenn Sie einen Controller pro Ressource verwenden). Wenn du tiefere Objektwelten erstellen würdest, wo würdest du die Klassen (und Module, nehme ich an) setzen? Ich frage nach Ansichten (in den Helfern selbst?), Controllern und Modellen.OO Design in Rails: Wo man Sachen hinlegt

Lib ist in Ordnung, und ich habe some solutions to get it to reload in a dev environment gefunden, aber ich würde gerne wissen, ob es eine bessere Möglichkeit gibt, dieses Zeug zu tun. Ich bin wirklich nur besorgt über Klassen, die zu groß werden. Was ist mit Motoren und wie passen sie zusammen?

Antwort

371

Da Rails Struktur in Bezug auf MVC bietet, ist es natürlich, nur die Modell-, Ansichts- und Controller-Container zu verwenden, die für Sie bereitgestellt werden. Das typische Idiom 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 stopfen.

An einem gewissen Punkt, weist jemand das „Fett-Modell, Skinny-Controller“ Paradigma und Zwischen Entwicklern in aller Eile Verbrauch alles von ihren Controllern und es in das Modell werfen, die eine neue Mülltonne für die Anwendungslogik zu werden beginnt, .

Skinny Controller sind in der Tat eine gute Idee, aber die logische Folge - alles in das Modell zu setzen, ist nicht wirklich der beste Plan.

In Ruby haben Sie ein paar gute Möglichkeiten, die Dinge modularer zu gestalten. Eine ziemlich populäre Antwort besteht darin, einfach Module zu verwenden (normalerweise in lib gespeichert), die Gruppen von Methoden enthalten, und dann die Module in die entsprechenden Klassen aufzunehmen. Dies hilft in Fällen, in denen Sie Funktionskategorien haben, die Sie in mehreren Klassen wiederverwenden möchten, bei denen die Funktionalität jedoch fiktiv noch den Klassen zugeordnet ist.

Denken Sie daran, wenn Sie ein Modul in eine Klasse gehören, werden die Methoden Instanzmethoden der Klasse, so dass Sie immer noch mit einer Klasse, die eine Tonne von Methoden am Ende, sind sie gut in mehrere Dateien einfach 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 nicht Modelle, Ansichten oder Controller sind.

Ein guter Weg, darüber nachzudenken, ist das "Prinzip der einheitlichen Verantwortung", das besagt, dass eine Klasse für eine einzelne (oder kleine) Anzahl von Dingen verantwortlich sein sollte. Ihre Modelle sind verantwortlich für das Fortbestehen von Daten aus Ihrer Anwendung in der Datenbank. Ihre Controller sind für das Empfangen einer Anfrage und das Zurückgeben einer brauchbaren Antwort verantwortlich.

Wenn Sie Konzepte haben, die nicht sauber in diese Boxen (Persistenz, Anfrage/Antwort-Management) passen, möchten Sie wahrscheinlich darüber nachdenken, wie Sie modellieren die Idee in Frage. Sie können nicht-Modellklassen in app/Klassen speichern, oder anderswo, und das Verzeichnis auf Ihrem Lastpfad hinzufügen, indem Sie:

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

Wenn Sie Passagier oder JRuby verwenden, werden Sie wahrscheinlich wollen auch Ihre hinzufügen Pfad zu den eifrigen Lastpfaden:

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

Die bottom-Line ist, dass, wenn Sie in Rails zu einem Punkt, wo man sich diese Frage finden, ist es Zeit, Ihr Ruby-Koteletts, Rindfleisch und Modellierung von Klassen zu starten, die aren Es sind nicht nur die MVC-Klassen, die Rails standardmäßig zur Verfügung stellt.

Aktualisierung: Diese Antwort bezieht sich auf Rails 2.x und höher.

+0

D'oh. Das Hinzufügen eines separaten Verzeichnisses für Nicht-Modelle war mir nicht eingefallen. Ich kann ein Aufräumen spüren ... –

+0

Yehuda, danke dafür. Gute Antwort.Genau das sehe ich in den Apps, die ich erben (und die, die ich mache): alles in Controllern, Modellen, Ansichten und den Helfern, die automatisch für Controller und Ansichten bereitgestellt werden. Dann kommen die Mixins von lib, aber es gibt nie einen Versuch, echte OO-Modellierung zu machen. Du hast aber Recht: in "Apps/Klassen oder irgendwo anders." Ich wollte nur nachsehen, ob es eine Standardantwort gibt, die ich vermisse ... –

+33

Bei neueren Versionen sind in config.autoload_paths standardmäßig alle Verzeichnisse unter App. Sie müssen also nicht wie oben beschrieben config.load_paths ändern. Ich bin jedoch nicht sicher über eager_load_paths (noch) und muss mich darum kümmern. Weiß jemand schon? –

58

Update: Die Verwendung von Bedenken wurden confirmed as the new default in Rails 4.

Es hängt wirklich von der Natur des Moduls selbst ab. Normalerweise plaziere ich Controller-/Modellerweiterungen in einem Ordner/belange in 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 allgemeine Bibliotheken. Ich habe immer einen Projektnamespace in lib, in dem ich alle anwendungsspezifischen Bibliotheken ablege.

Ruby/Rails-Kernerweiterungen finden normalerweise in Config-Initialisierungen statt, so dass 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, schaffe ich oft (micro) Plugins, so dass ich sie in anderen Projekten wiederverwenden können.

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

Dies ist ein wirklich allgemeiner Überblick. Bitte geben Sie weitere Details zu bestimmten Beispielen an, wenn Sie mehr benutzerdefinierte Vorschläge erhalten möchten. :)

+0

Bizarre Sache. Ich kann das nicht bekommen require_dependency RAILS_ROOT + "/ lib/mein_modul" mit etwas aus dem lib-Verzeichnis zu arbeiten. Es wird definitiv ausgeführt und beschwert sich, wenn die Datei nicht gefunden wird, aber sie wird nicht neu geladen. –

+0

Ruby's erfordern nur einmal die Dinge einmal zu laden. Wenn Sie etwas bedingungslos laden möchten, verwenden Sie Laden. – Chuck

+0

Außerdem scheint es mir ziemlich ungewöhnlich, dass Sie eine Datei während der Laufzeit einer App zweimal laden möchten. Generieren Sie Code, während Sie gehen? – Chuck

10

... die Tendenz, riesige Active Subklassen und riesige Controller zu machen ist ganz natürlich ...

"riesig" ist ein Anlass zur Sorge Wort ... ;-)

Wie werden Ihre Controller riesig? Das sollten Sie beachten: Controller sollten im Idealfall dünn sein. Wenn ich eine Faustregel aus der Luft schneide, würde ich vorschlagen, dass, wenn Sie regelmäßig mehr als 5 oder 6 Zeilen Code pro Controller-Methode (Aktion) haben, Ihre Controller wahrscheinlich zu fett sind. Gibt es eine Duplizierung, die sich in eine Hilfsfunktion oder einen Filter verschieben könnte? Gibt es eine Geschäftslogik, die in die Modelle hineingedrückt werden könnte?

Wie werden Ihre Modelle riesig? Sollten Sie nach Möglichkeiten suchen, die Anzahl der Verantwortlichkeiten in jeder Klasse zu reduzieren? Gibt es irgendwelche üblichen Verhaltensweisen, die Sie in Mixins extrahieren können? Oder Bereiche der Funktionalität, die Sie an Hilfsklassen delegieren können?

EDIT: Der Versuch, ein wenig zu erweitern, hoffentlich nichts zu verzerren zu schlecht ...

Helfer: leben in app/helpers und sind meist verwendeten Ansichten einfacher zu machen.Sie sind entweder Controller-spezifisch (auch für alle Ansichten dieses Controllers verfügbar) oder allgemein verfügbar (module ApplicationHelper in application_helper.rb).

Filter: Angenommen, Sie haben die selbe Codezeile in mehreren Aktionen (ziemlich oft, Abrufen eines Objekts mit params[:id] oder ähnlichem). Diese Duplizierung kann zuerst auf eine separate Methode und dann vollständig aus den Aktionen abstrahiert werden, indem ein Filter in der Klassendefinition deklariert wird, z. B. before_filter :get_object. Siehe Abschnitt 6 in der ActionController Rails Guide Lassen Sie die deklarative Programmierung Ihr Freund sein.

Refactoring-Modelle ist ein bisschen mehr eine religiöse Sache. Jünger von Uncle Bob schlagen zum Beispiel vor, dass Sie den Fünf Geboten von folgen. Joel & Jeff may recommend eine mehr, äh, "pragmatische" Ansatz, obwohl sie im Nachhinein ein little more reconciled sein schien. Das Auffinden einer oder mehrerer Methoden innerhalb einer Klasse, die mit einer klar definierten Teilmenge ihrer Attribute arbeiten, ist eine Möglichkeit, Klassen zu identifizieren, die aus Ihrem ActiveRecord-abgeleiteten Modell heraus refaktoriert werden könnten.

Rails-Modelle müssen übrigens keine Unterklassen von ActiveRecord :: Base sein. Oder anders ausgedrückt, ein Modell muss nicht analog zu einer Tabelle sein oder sich überhaupt auf etwas beziehen, das gespeichert wird. Noch besser, solange Sie Ihre Datei in app/models gemäß den Konventionen von Rails benennen (rufen Sie #underscore auf dem Klassennamen auf, um herauszufinden, nach was Rails suchen wird), wird Rails es finden, ohne dass require s notwendig ist.

+0

Wahr in allen Punkten, Mike, und danke für Ihre Bedenken ... Ich habe ein Projekt geerbt, in dem es einige Methoden auf Controllern gab, die riesig waren. Ich habe diese in kleinere Methoden aufgeteilt, aber der Controller selbst ist immer noch "fett". Also, was ich suche, sind alle meine Möglichkeiten, Sachen zu entladen. Ihre Antworten sind "Hilfsfunktionen", "Filter", "Modelle", "Mixins" und "Hilfsklassen". Also, wo kann ich diese Dinge hinstellen? Kann ich eine Klassenhierarchie organisieren, die in einem dev env automatisch geladen wird? –