OO Design in Rails: dove mettere le cose


244

Mi sto davvero divertendo con Rails (anche se in genere sono RESTless) e mi piace che Ruby sia molto OO. Tuttavia, la tendenza a creare enormi sottoclassi ActiveRecord e enormi controller è abbastanza naturale (anche se si utilizza un controller per risorsa). Se dovessi creare mondi di oggetti più profondi, dove metteresti le classi (e i moduli, suppongo)? Sto chiedendo informazioni su (negli Helpers stessi?), Controller e modelli.

Lib è a posto, e ho trovato alcune soluzioni per farlo ricaricare in un ambiente di sviluppo , ma mi piacerebbe sapere se c'è un modo migliore per fare queste cose. Sono davvero preoccupato solo per le classi che stanno diventando troppo grandi. Inoltre, che dire dei motori e come si adattano?

Risposte:


384

Poiché Rails fornisce una struttura in termini di MVC, è naturale finire per utilizzare solo i contenitori di modelli, viste e controller forniti. Il linguaggio tipico per i principianti (e anche per alcuni programmatori intermedi) è stipare tutta la logica nell'app nel modello (classe di database), controller o vista.

A un certo punto, qualcuno sottolinea il paradigma "modello grasso, controllore scarno" e gli sviluppatori intermedi escono in fretta tutto dai loro controller e lo gettano nel modello, che inizia a diventare un nuovo cestino per la logica dell'applicazione.

I controller skinny sono, in effetti, una buona idea, ma il corollario - mettere tutto nel modello, non è proprio il piano migliore.

In Ruby hai un paio di buone opzioni per rendere le cose più modulari. Una risposta abbastanza popolare è usare solo i moduli (di solito nascosti lib) che contengono gruppi di metodi e quindi includere i moduli nelle classi appropriate. Questo aiuta nei casi in cui si hanno categorie di funzionalità che si desidera riutilizzare in più classi, ma in cui la funzionalità è ancora teoricamente collegata alle classi.

Ricorda, quando includi un modulo in una classe, i metodi diventano metodi di istanza della classe, quindi finisci con una classe che contiene una tonnellata di metodi, sono semplicemente organizzati in modo semplice in più file.

Questa soluzione può funzionare bene in alcuni casi - in altri casi, vorrai pensare all'utilizzo di classi nel tuo codice che non siano modelli, viste o controller.

Un buon modo di pensarci è il "principio della singola responsabilità", secondo il quale una classe dovrebbe essere responsabile di un singolo (o piccolo numero) di cose. I tuoi modelli sono responsabili della persistenza dei dati dalla tua applicazione al database. I responsabili del trattamento sono responsabili della ricezione di una richiesta e della restituzione di una risposta valida.

Se si dispone di concetti che non rientrano esattamente in quelle scatole (persistenza, richiesta / risposta di gestione), probabilmente avrete bisogno di pensare a come si potrebbe modellare l'idea in questione. Puoi archiviare classi non modello in app / classi o in qualsiasi altro luogo e aggiungere quella directory al percorso di caricamento effettuando:

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

Se stai usando passeggero o JRuby, probabilmente vorrai anche aggiungere il tuo percorso ai percorsi di carico desiderosi:

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

La linea di fondo è che una volta arrivato a Rails in cui ti trovi a porre questa domanda, è tempo di rinforzare le tue costolette di Ruby e iniziare a modellare le classi che non sono solo le classi MVC che Rails ti offre di default.

Aggiornamento: questa risposta si applica a Rails 2.xe versioni successive.


D'oh. L'aggiunta di una directory separata per i non modelli non mi era venuta in mente. Riesco a sentire un ordine in arrivo ...
Mike Woodhouse,

Yehuda, grazie per quello. Bella risposta. Questo è esattamente ciò che vedo nelle app che eredito (e in quelle che realizzo): tutto in controller, modelli, viste e gli helper hanno fornito automaticamente controller e viste. Poi arrivano i mixin da lib, ma non c'è mai un tentativo di fare la vera modellazione OO. Hai ragione, però: in "app / classi o altrove". Volevo solo controllare se manca qualche risposta standard ...
Dan Rosenstark,

33
Con le versioni più recenti, config.autoload_paths imposta automaticamente tutte le directory dell'app. Quindi non è necessario modificare config.load_paths come descritto sopra. Non sono sicuro di eager_load_paths (ancora) però, e ho bisogno di esaminarlo. Qualcuno lo sa già?
Shyam Habarakada,

Passivo aggressivo verso gli intermedi: P
Sebastian Patten,

8
Sarebbe bello se Rails fosse spedito con questa cartella "classi" per incoraggiare il "principio di responsabilità singola" e consentire agli sviluppatori di creare oggetti che non sono supportati dal database. L'implementazione di "Preoccupazioni" in Rails 4 (vedi la risposta di Simone) sembra aver curato l'implementazione di moduli per condividere la logica tra i modelli. Tuttavia, tale strumento non è stato creato per le normali classi Ruby che non sono supportate dal database. Dato che Rails è molto supponente, sono curioso che il processo di pensiero dietro NON includa una cartella come questa?
Ryan Francis,

62

Aggiornamento : l'uso di Preoccupazioni è stato confermato come la nuova impostazione predefinita in Rails 4 .

Dipende molto dalla natura del modulo stesso. Di solito inserisco le estensioni del controller / modello in una cartella / concernente l'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 è la mia scelta preferita per le librerie di uso generale. Ho sempre uno spazio dei nomi di progetto in lib in cui inserisco tutte le librerie specifiche dell'applicazione.

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

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

Le estensioni core di Ruby / Rails di solito hanno luogo negli inizializzatori di configurazione in modo che le librerie vengano caricate una sola volta sul boostrap di Rails.

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

Per frammenti di codice riutilizzabili, creo spesso plug-in (micro) in modo da poterli riutilizzare in altri progetti.

I file helper di solito contengono metodi helper e talvolta classi quando l'oggetto è destinato ad essere utilizzato dagli helper (ad esempio Form Builders).

Questa è una panoramica davvero generale. Fornisci maggiori dettagli su esempi specifici se desideri ottenere suggerimenti più personalizzati. :)


Cosa bizzarra. Non riesco a ottenere questo requisito_dipendenza RAILS_ROOT + "/ lib / my_module" per lavorare con qualcosa fuori dalla directory lib. Esegue sicuramente e si lamenta se il file non viene trovato, ma non lo ricarica.
Dan Rosenstark,

Il Ruby richiede di caricare le cose solo una volta. Se vuoi caricare qualcosa incondizionatamente, usa load.
Chuck,

Inoltre, mi sembra abbastanza insolito che tu voglia caricare un file due volte durante la vita di un'istanza di un'app. Stai generando codice mentre procedi?
Chuck,

Perché usi Require_Dependency invece di Require? Si noti inoltre che se si seguono le convenzioni di denominazione non è necessario utilizzare affatto. Se crei MyModule in lib / my_module, puoi invocare MyModule senza necessità precedente (anche se l'utilizzo di request dovrebbe essere più veloce e talvolta più leggibile). Si noti inoltre che i file in / lib vengono caricati una sola volta sul bootstrap.
Simone Carletti,

1
L'uso delle preoccupazioni riguarda
bbozo il

10

... la tendenza a creare enormi sottoclassi ActiveRecord e enormi controller è abbastanza naturale ...

"enorme" è una parola preoccupante ... ;-)

In che modo i tuoi controller stanno diventando enormi? È qualcosa che dovresti guardare: idealmente, i controller dovrebbero essere magri. Scegliendo una regola empirica dal nulla, suggerirei che se hai regolarmente più di, per esempio, 5 o 6 righe di codice per metodo del controller (azione), probabilmente i tuoi controller sono troppo grassi. Esistono duplicazioni che potrebbero spostarsi in una funzione di supporto o in un filtro? Esiste una logica aziendale che potrebbe essere inserita nei modelli?

In che modo i tuoi modelli diventano enormi? Dovresti cercare modi per ridurre il numero di responsabilità in ogni classe? Ci sono comportamenti comuni che puoi estrarre nei mixin? O aree di funzionalità che è possibile delegare alle classi di supporto?

EDIT: Cercando di espandersi un po ', speriamo di non distorcere qualcosa di troppo male ...

Aiutanti: vivono app/helperse sono principalmente utilizzati per semplificare le visualizzazioni. Sono specifici del controller (disponibili anche per tutte le visualizzazioni per quel controller) o generalmente disponibili ( module ApplicationHelperin application_helper.rb).

Filtri: supponi di avere la stessa riga di codice in diverse azioni (abbastanza spesso, recupero di un oggetto usando params[:id]o simili). Tale duplicazione può essere prima astratta in un metodo separato e poi completamente fuori dalle azioni dichiarando un filtro nella definizione della classe, ad esempio before_filter :get_object. Vedere la sezione 6 della Guida alle rotaie di ActionController. Lascia che la programmazione dichiarativa sia tua amica.

I modelli di refactoring sono un po 'più una cosa religiosa. I discepoli di zio Bob suggeriranno, ad esempio, di seguire i cinque comandamenti di SOLID . Joel & Jeff possono raccomandare un approccio più "pragmatico", anche se in seguito sembravano essere un po 'più riconciliati . Trovare uno o più metodi all'interno di una classe che operano su un sottoinsieme chiaramente definito dei suoi attributi è un modo per provare a identificare le classi che potrebbero essere rifattorizzate dal modello derivato da ActiveRecord.

A proposito, i modelli Rails non devono essere sottoclassi di ActiveRecord :: Base. O, per dirla in altro modo, un modello non deve essere un analogo di una tabella, o addirittura correlato a qualcosa memorizzato. Ancora meglio, fintanto che app/modelsassegni il tuo file secondo le convenzioni di Rails (chiama #underscore sul nome della classe per scoprire cosa cercherà Rails), Rails lo troverà senza requireche sia necessario.


Vero sotto tutti gli aspetti, Mike, e grazie per la tua preoccupazione ... Ho ereditato un progetto in cui c'erano alcuni metodi sui controller che erano enormi. Li ho suddivisi in metodi più piccoli ma il controller stesso è ancora "grasso". Quindi quello che sto cercando sono tutte le mie opzioni per scaricare roba. Le tue risposte sono "funzioni di supporto", "filtri", "modelli", "mixin" e "classi di supporto". Quindi, dove posso mettere queste cose? Posso organizzare una gerarchia di classi che viene caricata automaticamente in un ambiente di sviluppo?
Dan Rosenstark,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.