Nozioni di base AntiPatterns controller di rotaie

Se hai vissuto sulla dieta "grassi modelli, magri controllori", hai sicuramente andando nella giusta direzione. Mantenere i controller magri, però, non è così facile come sembra. In questo articolo imparerai un paio di approcci per principianti per perdere grasso del controller.

Temi

  • Controllori grassi
  • Controller non RESTful
  • Rat's Nest Resources

Controller FAT

Bene, "modelli grassi, controllori magri", giusto? Nel caso in cui non abbiate letto i precedenti articoli di AntiPattern, dovrei menzionare che puntare a modelli e controller che restano magri è una linea guida migliore, a prescindere da cosa. Tutto quel grasso in eccesso non fa bene ai tuoi progetti - "tutto magro" ha molto più senso. (Forse dovrei chiarire che non sono associato al mondo della moda in alcun modo e non voglio ripetere l'impressione che non puoi essere considerato bello senza un certo tipo di corpo immaginario.) 

Come con i modelli, vuoi controller che hanno responsabilità singole. I controller dovrebbero essere davvero stupidi, gestendo il traffico e non molto altro. Inoltre, se possibile, vogliamo rendere i nostri modelli il più stupidi possibile-i presentatori possono essere utili a tale riguardo.

È inoltre importante non allontanarsi troppo dalle azioni del controller RESTful. Certo, ogni tanto può avere senso avere metodi aggiuntivi, ma la maggior parte delle volte, dovresti sentirti un po 'a disagio averli intorno. I controllori tendono a ingrassare quando accumulano una logica di business che in realtà appartiene ai modelli o quando gli sviluppatori inesperti non fanno uso delle convenzioni di Rails. 

Non sarai il primo a cercare di reinventare la ruota, e certamente non sarai l'ultimo. Non ti preoccupare, perché probabilmente la maggior parte di noi è stata lì, ma come artigiano, dovresti davvero investire tempo e sforzi per conoscere le convenzioni, i vantaggi e i limiti delle strutture con cui lavori, almeno per scopi commerciali dove qualcuno paga per la tua esperienza. Gli esperimenti vanno sempre bene, ovviamente.

Poiché i responsabili del controllo sono responsabili del flusso della tua applicazione e raccolgono le informazioni necessarie per le tue visualizzazioni, hanno già una responsabilità importante. Non hanno davvero bisogno di aggiungere complessità dal regno dei tuoi modelli. I controller lavorano strettamente con le visualizzazioni per visualizzare i dati forniti dal livello del modello. La loro relazione è più stretta rispetto ai modelli. Il livello del modello può potenzialmente essere sviluppato in modo molto più indipendente dagli altri. La cosa positiva è che un livello di controller pulito spesso ha un effetto positivo sulla precisione delle visualizzazioni.

Quello che voglio trasmettere è che i grassi controller sono molto comuni nella terra di Rails - specialmente tra principianti e sviluppatori inesperti - e con un po 'di amore e cura, questo può essere facilmente ottimizzato. 

Il primo passo è semplice. Chiediti quando un controller cresce di dimensioni se la complessità deriva dalla logica aziendale aggiunta. In tal caso, trovare un modo per spostarlo sul livello del modello in cui si ha il vantaggio di avere una casa migliore per testare codice complesso.

presentatori

Per seguire la suddetta raccomandazione di spostare la logica del controller acquisita sui modelli, i relatori possono essere una tecnica a portata di mano. Possono simulare un modello combinando insieme un paio di attributi liberamente associati, il che può essere utile per mantenere i controller snelli e sexy. Oltre a ciò, sono anche bravi a mantenere la cattiva cattiveria fuori dai tuoi punti di vista. Abbastanza buono per creare un oggetto extra!

I presentatori possono "imitare" un modello che rappresenta lo stato di cui ha bisogno la tua vista e combina gli attributi che devono muoversi attraverso il controller. Possono essere più complessi, ma poi sento che stanno andando alla deriva nel territorio "Decoratore". 

A volte un controllore è responsabile della creazione di più modelli contemporaneamente e vogliamo evitare che gestisca più variabili di istanza. Perché questo è importante? Perché ci aiuta a mantenere sotto controllo la manutenibilità delle nostre app. Il relatore aggrega il comportamento e gli attributi, il che rende facile per i nostri controllori concentrarsi su lavori piccoli, semplici e mortali, con un singolo oggetto. Inoltre, la formattazione dei dati nella visualizzazione o altre simili funzioni di piccole dimensioni sono spesso lavori frequenti. Avere questo contenuto in un relatore non è solo un grande vantaggio per le viste nitide, ma anche per avere un luogo dedicato che rende semplice testare questo comportamento: il livello del modello è più facile da testare. Più "bang for the buck" e tutto quel jazz.

Se incappi nel Pattern Presenter e trovi approcci multipli o modi diversi di descriverlo, non stai impazzendo. Sembra esserci un accordo chiaro su cosa sia un presentatore. Ciò che è noto, tuttavia, è che si trova tra i livelli MVC. Possiamo usarlo per gestire più oggetti del modello che devono essere creati allo stesso tempo. Combinando questi oggetti, imita un modello di ActiveRecord. 

Uno scenario comunemente citato è una sorta di modulo che immette informazioni per vari modelli diversi, come un nuovo account utente che ha anche campi di input per carte di credito e indirizzi o qualcosa del genere. Passare alla procedura guidata completa passando da un paio di moduli in sequenza non è poi così diverso. Dato che queste parti della tua applicazione tendono a essere molto importanti, è sicuramente una buona idea mantenere le cose in ordine pur avendo la migliore opzione disponibile per testare allo stesso tempo. 

Anche l'esperienza dell'utente su questo è fondamentale. Nell'esempio seguente, vogliamo creare una missione semplice Ha uno agente e uno furiere. Nessuna scienza missilistica, ma è un buon esempio per vedere quanto velocemente le cose possono sfuggire di mano. Il controller deve destreggiarsi tra più oggetti di cui la vista ha bisogno in una forma nidificata per legare insieme le cose. Presto vedrai che tutto questo può essere risolto con un bel "Oggetto Form" che presenta gli oggetti necessari e intreccia le cose insieme in una classe centrale.

app / modelli / mission.rb

classe missione < ActiveRecord::Base has_one :agent has_one :quartermaster accepts_nested_attributes_for :agent, :quartermaster, allow_destroy: true validates :mission_name, presence: true… end class Agent < ActiveRecord::Base belongs_to :mission validates :name, presence: true… end class Quartermaster < ActiveRecord::Base belongs_to :mission validates :name, presence: true… end

Sto menzionando i modelli qui solo per motivi di completezza nel caso non l'abbia mai usato fields_for prima - un po 'semplificato ma funzionante. Di seguito è il cuore della questione. 

Troppe variabili di istanza

app / controllers / missions_controller.rb

class MissionsController < ApplicationController def new @mission = Mission.new @agent = Agent.new @quartermaster = Quartermaster.new end def create @mission = Mission.new(mission_params) @agent = Agent.new(agent_params) @quartermaster = Quartermaster.new(quartermaster_params) @mission.agent = @agent @mission.quartermaster = @quartermaster if @account.save and @agent.save and @quartermaster.save flash[:notice] = 'Mission accepted' redirect_to missions_path else flash[:alert] = 'Mission not accepted' render :new end end private def mission_params params.require(:mission).permit(:mission_name, :objective, :enemy) end def agent_params params.require(:agent).permit(:name, :number, :licence_to_kill) end def quartermaster_params params.require(:quartermaster).permit(:name, :number, :hacker, :expertise, :humor) end end

Nel complesso, è facile vedere che questo sta andando nella direzione sbagliata. È già attratto un bel po 'di massa e consiste solo di nuovo e creare metodi. Non bene! I metodi privati ​​si stanno già accumulando troppo velocemente. avere agent_params e quartermaster_params in un MissionsController non sembra troppo lucido per te, spero. Una visione rara, pensi? Temo di no. Le "singole responsabilità" nei controller sono davvero una linea guida d'oro. Vedrai perché in un minuto.

Anche se ti strizzi gli occhi, questo sembra super cattivo. E durante il salvataggio in creare azione, con validazioni in atto, se non possiamo salvare ogni oggetto a causa di un errore o qualcosa del genere, finiremo con oggetti orfani che nessuno vuole affrontare. yikes! 

Certo, potremmo metterlo in un transazione block, che completa con successo il salvataggio solo se tutti gli oggetti sono in ordine, ma questo è un po 'come navigare contro corrente, anche perché roba di questo tipo nel controller, davvero? Ci sono modi più eleganti per catturare un'onda.

Seguendo questo percorso, la vista avrebbe un accompagnamento form_for per @missione e l'aggiuntivo fields_for per @agente e @quartermaster ovviamente.

Forma disordinata con oggetti multipli

app / views / missioni / new.html.erb

<%= form_for(@mission) do |mission| %> 

Missione

<%= mission.label :mission_name %> <%= mission.text_field :mission_name %> <%= mission.label :objective %> <%= mission.text_field :objective %> <%= mission.label :enemy %> <%= mission.text_field :enemy %>

Agente

<%= fields_for @agent do |agent| %> <%= agent.label :name %> <%= agent.text_field :name %> <%= agent.label :number %> <%= agent.text_field :number %> <%= agent.label :licence_to_kill %> <%= agent.check_box :licence_to_kill %> <% end %>

furiere

<%= fields_for @quartermaster do |quartermaster| %> <%= quartermaster.label :name %> <%= quartermaster.text_field :name %> <%= quartermaster.label :number %> <%= quartermaster.text_field :number %> <%= quartermaster.label :hacker %> <%= quartermaster.check_box :hacker %> <%= quartermaster.label :expertise %> <%= quartermaster.text_field :expertise %> <%= quartermaster.label :humor %> <%= quartermaster.check_box :humor %> <% end %> <%= mission.submit %> <% end %>

Certo, funziona, ma non sarei troppo eccitato per incappare in questo. fields_for certo è comodo e tutto, ma gestirlo con OOP è molto più dannoso. Per un caso del genere, un presentatore ci aiuterà anche ad avere una vista più semplice perché il modulo si occuperà solo di un singolo oggetto. Annidare la forma diventa inutile in questo modo. A proposito, ho omesso qualsiasi involucro per lo styling del modulo per renderlo più facile da digerire visivamente. 

Form Object Presenter

app / views / missioni / new.html.erb

<%= form_for @mission_presenter, url: missions_path do |mission| %> 

Missione

<%= mission.label :mission_name %> <%= mission.text_field :mission_name %> <%= mission.label :objective %> <%= mission.text_field :objective %> <%= mission.label :enemy %> <%= mission.text_field :enemy %>

Agente

<%= mission.label :agent_name %> <%= mission.text_field :agent_name %> <%= mission.label :agent_number %> <%= mission.text_field :agent_number %> <%= mission.label :licence_to_kill %> <%= mission.check_box :licence_to_kill %>

furiere

<%= mission.label :quartermaster_name %> <%= mission.text_field :quartermaster_name %> <%= mission.label :quartermaster_number %> <%= mission.text_field :quartermaster_number %> <%= mission.label :hacker %> <%= mission.check_box :hacker %> <%= mission.label :expertise %> <%= mission.text_field :expertise %> <%= mission.label :humor %> <%= mission.check_box :humor %> <%= mission.submit %> <% end %>

Come puoi facilmente vedere, la nostra vista è diventata molto più semplice, senza nidificazione, ed è molto più semplice questo piatto. La parte che devi essere un po 'attenta è questa:

<%= form_for @mission_presenter, url: missions_path do |mission| %>

È necessario fornire form_for con un percorso via url in modo che possa "postare" i parametri da questo modulo al proprio controller, qui MissionsController. Senza questo argomento aggiuntivo, Rails cercherebbe di trovare il controller per il nostro oggetto presentatore @mission_presenter attraverso le convenzioni, in questo caso MissionFormPresentersController-e saltare in aria senza uno.

In generale, dovremmo fare del nostro meglio per mantenere le azioni del controllore per lo più semplici come trattare con la manipolazione delle risorse CRUD - questo è ciò che un controller fa per vivere ed è il meglio equipaggiabile per fare a meno di confondere le distinzioni MVC. Come un bell'effetto collaterale, anche il livello di complessità nei controller si ridurrà.

app / controllers / missions_controller.rb

class MissionsController < ApplicationController def new @mission_presenter = MissionFormPresenter.new end def create @mission_presenter = MissionFormPresenter.new(mission_params) if @mission_presenter.save flash[:notice] = 'Mission accepted' redirect_to missions_path else flash[:alert] = 'Mission not accepted' render :new end end private def mission_params params.require(:mission_form_presenter).permit(whitelisted) end def whitelisted [:mission_name, :objective, :enemy, :agent_name, :agent_number, :licence_to_kill, :quartermaster_name, :quartermaster_number, :hacker, :expertise, :humor] end end

Il controller è anche molto più facile per gli occhi, non è vero? Azioni controller più pulite e più o meno standard. Abbiamo a che fare con un singolo oggetto con un solo lavoro. Istanziamo un singolo oggetto, il presentatore, e lo alimentiamo come al solito.

L'unica cosa che mi ha infastidito è stata l'invio di questa lunga lista di potenti parametri inseriti nella whitelist. Li ho estratti in un metodo chiamato whitelist, che restituisce semplicemente un array con l'elenco completo dei parametri. Altrimenti, mission_params sarebbe sembrato il seguente - che sembrava troppo cattivo:

def mission_params params.require (: mission_form_presenter) .permit (: nome_azione,: obiettivo,: nemico,: nome_agente,: numero_agente,: licence_to_kill,: quartermaster_name,: quartermaster_number,: hacker,: competenza,: umorismo) fine

Oh, una parola sul : mission_form_presenter argomento per params.require. Sebbene abbiamo chiamato la nostra variabile di istanza per il presentatore @mission_presenter, quando lo usiamo con form_for, Rails si aspetta che la chiave dell'hash params per il modulo venga denominata dopo l'istanza dell'oggetto, non dopo il nome fornito in un controller. Ho visto i neofiti inciampare su questo diverse volte. That Rails ti sta fornendo errori criptici in questo caso non aiuta neanche. Se hai bisogno di un piccolo aggiornamento su params, questo è un buon posto per scavare:

  • Documentazione
  • Screencast gratuito

Nel nostro Missione modello, ora non ne abbiamo bisogno accepts_nested_attributes più e può sbarazzarsi di quella cosa dall'aspetto innocuo e temuto. Il Convalida anche il metodo è irrilevante perché aggiungiamo questa responsabilità al nostro oggetto modulo. Lo stesso vale per le nostre convalide Agente e furiere, ovviamente.

app / modelli / mission.rb

classe missione < ActiveRecord::Base has_one :agent has_one :quartermaster #accepts_nested_attributes_for :agent, :quartermaster, allow_destroy: true #validates :mission_name, presence: true… end

Incapsulare questa logica di convalida direttamente sul nostro nuovo oggetto ci aiuta a mantenere le cose pulite e organizzate. Nei casi in cui è anche possibile creare questi oggetti indipendentemente l'uno dall'altro, le convalide dovrebbero rimanere dove sono attualmente, naturalmente. Questo tipo di duplicazione può anche essere affrontato, ad esempio utilizzando validates_with con una classe separata per la convalida che eredita da ActiveModel :: Validator.

Ora abbiamo un controller magro con una sola responsabilità e una forma piatta per la creazione di più oggetti in parallelo. Eccezionale! Come abbiamo raggiunto tutto questo? Di seguito c'è il presentatore che fa tutto il lavoro, non che questo implica molto lavoro, comunque. Vogliamo avere una sorta di modello intermedio senza un database che manipoli più oggetti. Dai un'occhiata a questo semplice oggetto vecchio rubino (PORO).

app / presentatori / mission_form_presenter.rb

class MissionFormPresenter include ActiveModel :: Model attr_accessor: mission_name,: objective,: enemy,: nome_agente,: numero_agente,: licence_to_kill,: quartermaster_name,: quartermaster_number,: hacker,: competenza,: umorismo: mission_name,: nome_agente,: quartermaster_name, presenza: true def save ActiveRecord :: Base.transaction do @mission = Mission.create! (mission_attributes) @ mission.create_agent! (agent_attributes) @ mission.create_quartermaster! (quartermaster_attributes) end end private def mission_attributes nome_azione: nome_azione, obiettivo: obiettivo, nemico: nemico end def agent_attributes nome: nome_agente, numero: numero_agente, licence_to_kill: licence_to_kill end def quartermaster_attributes nome: quartermaster_name, numero: quartermaster_number, hacker: hacker, competenza: competenza, umorismo: umorismo fine fine

Penso che sia giusto dire che non è molto complicato. MissionFormPresenter è un oggetto modulo che ora incapsula ciò che ha reso il nostro controller inutilmente grasso. Come bonus, il nostro punto di vista è diventato piatto e semplice. Quello che succede qui è che possiamo aggregare tutte le informazioni dal nostro modulo e quindi creare tutti gli oggetti di cui abbiamo bisogno in sequenza.

Il pezzo più importante succede nel nostro nuovo salvare metodo. Per prima cosa creiamo il nuovo Missione oggetto. Successivamente, possiamo creare i due oggetti ad esso associati: Agente e furiere. Attraverso il nostro Ha uno e appartiene a associazioni, possiamo fare uso di a create_x metodo che si adatta al nome dell'oggetto associato. 

Ad esempio, se usiamo has_one: agent, otteniamo a create_agent metodo. Facile, giusto? (In realtà anche noi otteniamo un build_agent metodo.) Ho deciso di usare la versione con un botto (!) perché solleva un ActiveRecord :: RecordInvalid errore se il record non è valido mentre si tenta di salvare. Avvolto dentro a transazione blocco, questi metodi bang fanno in modo che nessun oggetto orfano venga salvato se viene eseguito qualche convalida. Il blocco della transazione verrà ripristinato se qualcosa va storto durante il salvataggio. 

Come funziona con gli attributi, potresti chiedere? Tramite Rails chiediamo un po 'd'amore include ActiveModel :: Model (API). Questo ci permette di inizializzare oggetti con un hash di attributi, che è esattamente ciò che facciamo nel controller. Dopo, possiamo usare il nostro attr_accessor metodi per estrarre i nostri attributi per istanziare gli oggetti di cui abbiamo veramente bisogno.

ActiveModel :: Modello ci consente inoltre di interagire con visualizzazioni e controllori. Tra le altre cose, puoi anche usare questo per le convalide in tali classi. Mettere queste convalide in tali oggetti di forma dedicati è una buona idea per l'organizzazione, e inoltre mantiene i tuoi modelli un po 'più ordinati. 

Ho deciso di estrarre la lunga lista di parametri in metodi privati ​​che alimentano gli oggetti che vengono creati salvare. In un oggetto di questo presentatore, ho poca preoccupazione di avere un paio di altri metodi privati ​​in giro. Perchè no? Sembra più pulito!

Testare questo tipo di scenari in cui più modelli si uniscono devono essere trattati con la massima cura: più semplici sono gli oggetti in questione, migliore è l'esperienza di test. Nessuna scienza missilistica, davvero. I presentatori operano a tuo favore su questo. Avere questi test potenzialmente legati al controller non è il modo migliore per avvicinarsi a questo. Ricorda, i test unitari sono rapidi ed economici.

Una parola di cautela. Non abusare dei presentatori: non dovrebbero essere la tua prima scelta. Di solito, la necessità di un presentatore cresce nel tempo. Per me personalmente, vengono utilizzati al meglio quando i dati sono rappresentati da più modelli che devono riunirsi in un'unica vista. 

Senza un relatore, è possibile preparare più variabili di istanza nel controller per un'unica vista. Questo da solo può renderli davvero grassi, molto veloci. Una cosa che dovresti considerare e valutare è che mentre i presentatori aggiungono oggetti alla base di codice, possono anche ridurre il numero di oggetti di cui un controller deve occuparsi, meno complessità e singole responsabilità. Probabilmente è una tecnica abbastanza avanzata per perdere un po 'di grasso, ma quando vuoi dimagrire, devi metterti al lavoro. 

Controller non RESTful

Probabilmente non cercare di aderire alle azioni del controller standard è una cattiva idea. Avere tonnellate di metodi di controllo personalizzati è un AntiPattern che puoi evitare facilmente. Metodi come login_user, activate_admin, show_books, e altri affari divertenti che stanno per nuovo, creare, mostrare e così via, dovrebbe darti una ragione per mettere in pausa e dubitare del tuo approccio. Non seguire un approccio RESTful può facilmente portare a controllori massicci, molto probabilmente perché dovrai combattere la struttura o reinventare la ruota di tanto in tanto. 

In breve, non è una buona idea. Inoltre, il più delle volte, è un sintomo di inesperienza o disattenzione. Seguire il "Principio della singola responsabilità" sembra essere molto difficile anche in queste circostanze, ma solo un'ipotesi plausibile.

Avvicinarsi alle risorse del tuo controller in modo RESTful ti rende la vita molto meno complicata e le tue app più facili da mantenere, il che aumenta la stabilità generale della tua app. Pensa a gestire le risorse RESTfully dal punto di vista del ciclo di vita di un oggetto. Puoi creare, aggiornare, mostrare (singoli o raccolte), aggiornarli e distruggerli. 

Per la maggior parte dei casi, questo farà il lavoro. FYI, nuovo e modificare le azioni non fanno realmente parte di REST - sono più simili alle diverse versioni di mostrare azione, aiutandoti a presentare diverse fasi del ciclo di vita della risorsa. Messi insieme, la maggior parte delle volte, queste sette azioni standard del controller ti danno tutto il necessario per gestire le risorse nei controller. Un altro grande vantaggio è che gli altri sviluppatori di Rails che lavorano con il tuo codice saranno in grado di navigare i tuoi controller molto più velocemente.

Seguendo questa linea di aiuto fresco RESTful, questo include anche il modo in cui si nominano i controller. Il nome della risorsa su cui si lavora dovrebbe essere speculare nell'oggetto controller. 

Ad esempio, avendo a MissionsController che gestisce altre risorse di @missione gli oggetti sono un odore che qualcosa non va. La dimensione pura di un controller spesso è anche un regalo morto che REST è stato ignorato. Se si incontrano controller di grandi dimensioni che implementano tonnellate di metodi personalizzati che rompono con le convenzioni, può essere una strategia molto efficace per suddividerli in più controller distintivi che hanno responsabilità focalizzate e sostanzialmente gestire solo una singola risorsa mentre si aderisce a uno stile RESTful. Spezzali in modo aggressivo e avrai più tempo per comporre i loro metodi in modo Rails.

Rat's Nest Resources

Guarda il seguente esempio e chiediti cosa c'è di sbagliato in questo:

Nested AgentsController

app / controllers / agents_controller.rb

Class AgentsController < ApplicationController def index if params[:mission_id] @mission = Mission.find(params[:mission_id]) @agents = @mission.agents else @agents = Agent.all end end end

Qui controlliamo se abbiamo una rotta nidificata che ci fornisce l'id per un possibile @missione oggetto. Se è così, vogliamo usare l'oggetto associato per ottenere il agenti da. Altrimenti, recupereremo un elenco di tutti gli agenti per la vista. Sembra innocuo, soprattutto perché è ancora conciso, ma è l'inizio di un nido di topi potenzialmente molto più grande.

Percorsi nidificati

risorse: risorse degli agenti: le missioni fanno risorse: gli agenti terminano

Niente di niente per le rotte nidificate qui. In generale, non c'è nulla di sbagliato in questo approccio. La cosa su cui dovremmo stare attenti è il modo in cui il controller gestisce questo business e, di conseguenza, come la vista deve adattarsi ad esso. Non esattamente perfettamente pulito, come puoi vedere qui sotto.

Visualizza con condizionale non necessario

app / views / agenti / index.html.erb

<% if @mission %> 

Missione

<%= @mission.name %>
<%= @mission.objective %>
<%= @mission.enemy %>
<% end %>

Agents

    <% @agents.each do |agent| %>
  • Nome: <%= agent.name %>
    Numero: <%= agent.number %>
    Licenza per uccidere: <%= agent.licence_to_kill %>
    Stato: <%= agent.status %>
  • <% end %>

Potrebbe anche non sembrare un grosso problema, ho capito. Tuttavia, il livello di complessità non è esattamente il mondo reale. A parte questo, l'argomento riguarda più le risorse in un modo orientato agli oggetti e l'utilizzo di Rails al massimo vantaggio. 

Immagino che questo sia un piccolo caso per quanto riguarda le singole responsabilità. Non sta violando esattamente questa idea, anche se abbiamo un secondo oggetto-@missione-per l'associazione indugiando in giro. Ma dal momento che lo stiamo usando per ottenere l'accesso a una serie specifica di agenti, questo è assolutamente accettabile.

La ramificazione è la parte poco elegante e molto probabilmente porterà a decisioni di progettazione scadenti, sia nelle visualizzazioni che nei controller. Creazione di due versioni di @agents nello stesso modo è il perpetratore qui. Lo farò breve, questo può sfuggire di mano molto velocemente. Una volta che inizi a nidificare risorse come questa, è probabile che i nuovi ratti siano in giro presto. 

E la vista sopra ha anche bisogno di un condizionale che si adatti alla situazione per quando ne hai @agents associato a a @missione. Come si può facilmente vedere, un po 'di trascuratezza nel controller può portare a visioni gonfie che hanno più codice del necessario. Proviamo un altro approccio. Tempo sterminatore! 

Controller separati

Invece di annidare queste risorse, dovremmo assegnare a ciascuna versione di questa risorsa un controller specifico e focalizzato, un controller per agenti "semplici" e non verificati e uno per gli agenti associati a una missione. Possiamo ottenere questo tramite il namespace uno di loro sotto a / missioni cartella. 

app / controllers / missioni / agents_controller.rb

modulo Missioni classe AgentsController < ApplicationController def index @mission = Mission.find(params[:mission_id]) @agents = @mission.agents end end end

Con il wrapping di questo controller all'interno di un modulo, possiamo evitare di avere AgentsController eredita due volte da ApplicationController. Senza di esso, corriamo in un errore come questo: Impossibile eseguire il caricamento automatico delle missioni :: AgentsController. Penso che un modulo sia un piccolo prezzo da pagare per rendere felice l'autoloading di Rails. Il secondo AgentsController può rimanere nello stesso file di prima. Ora si occupa solo di una possibile risorsa in indice-preparare tutti gli agenti senza le missioni che ci sono in giro. 

app / controllers / agents_controller.rb

Class AgentsController < ApplicationController def index @agents = Agent.all end end 

Ovviamente, dobbiamo anche indicare ai nostri percorsi di cercare questo nuovo controller con nomi assegnati se gli agenti sono associati a una missione.

risorse: risorse agenti: le missioni fanno risorse: agenti, controllore: fine 'missioni / agenti'

Dopo aver specificato che la nostra risorsa nidificata ha un controller con nomi diversi, siamo pronti. Quando facciamo a percorsi di rake controlla il terminale, vedremo che il nostro nuovo controller è nominato per nome e che siamo a posto.

Nuove rotte

 Prefix Verb URI Pattern Controller # Root d'azione GET / agenti # index agent GET /agents(.:format) agenti # indice POST /agents(.:format) agenti # create new_agent GET /agents/new(.:format) agenti # new edit_agent GET /agents/:id/edit(.:format) agent # edit agent GET /agents/:id(.:format) agent # show PATCH /agents/:id(.:format) agent # update PUT / agents / : id (.: format) agent # update DELETE /agents/:id(.:format) agents # destroy mission_agents GET /missions/:mission_id/agents(.:format) missions / agents # index POST / missions /: mission_id / agenti (.: formato) missioni / agenti # crea new_mission_agent GET /missions/:mission_id/agents/new(.format) missions / agents # new edit_mission_agent GET /missions/:mission_id/agents/:id/edit(.:format ) missions / agents # edit mission_agent GET /missions/:mission_id/agents/:id(.:format) missions / agents # show PATCH /missions/:mission_id/agents/:id(.:format) missions / agents # update PUT /missions/:mission_id/agents/:id(.:format) missions / agents # update DELETE / missions /: mission_id / agents / : id (.: format) missions / agents # destroy

La nostra risorsa nidificata per agenti ora è correttamente reindirizzato a controllori / missioni / agents_controller.rb e ogni azione può prendersi cura degli agenti che fanno parte di una missione. Per completezza, diamo un'occhiata anche alle nostre opinioni finali:

Agenti con missione

app / views / missioni / agenti / index.html.erb 

Missione

<%= @mission.mission_name %>
<%= @mission.objective %>
<%= @mission.enemy %>

Agents

    <% @agents.each do |agent| %>
  • Nome: <%= agent.name %>
    Numero: <%= agent.number %>
    Licenza per uccidere: <%= agent.licence_to_kill %>
  • <% end %>

Agenti senza missione

app / views / agenti / index.html

Agents

    <% @agents.each do |agent| %>
  • Nome: <%= agent.name %>
    Numero: <%= agent.number %>
    Licenza per uccidere: <%= agent.licence_to_kill %>
  • <% end %>

Bene, liberiamoci di quel poco di duplicazione su cui andiamo a scorrere @agents anche. Ho creato una parte per il rendering di un elenco di agenti e l'ho inserito in un nuovo condivisa directory sotto visualizzazioni

app / views / shared / _agents.html.erb

Agents

    <% @agents.each do |agent| %>
  • Nome: <%= agent.name %>
    Numero: <%= agent.number %>
    Licenza per uccidere: <%= agent.licence_to_kill %>
  • <% end %>

Niente di nuovo o sorprendente qui, ma i nostri punti di vista ora sono più ASCIUTTI.

Agenti con missione

app / views / missio