Elementele de baza ale AntiPatterns Controlere Rails

Dacă ați trăit în regimul "modelele grase, controlorii subțiri", cu siguranță mergeți în direcția cea bună. Păstrarea controlorilor slabi, totuși, nu este la fel de ușoară ca sună. În acest articol veți afla câteva abordări prietenoase începătorilor pentru pierderea unor grăsimi controler.

Subiecte

  • Controlere de grăsime
  • Controlere non-RESTful
  • Rat's Nest Resources

Controlere FAT

Ei bine, "modelele grase, controlorii slabi", nu? În cazul în care nu ați citit articolele precedente AntiPattern, trebuie să menționez că obiectivul pentru modelele și controlorii care rămân slabi este o orientare mai bună - indiferent de ce. Tot ceea ce excesul de grăsimi nu este bun pentru proiectele dvs. - "totul slăbit" are mult mai mult sens. (Poate că ar trebui să clarific faptul că nu sunt asociat în nici un fel cu industria modei și nu vreau să repet impresia că nu poți fi considerat frumos fără a se potrivi unui anumit tip de corp imaginar.) 

Ca și în cazul modelelor, doriți controlorii care au responsabilități unice. Controlorii ar trebui să fie cu adevărat prost, gestionând traficul și nu mai mult. De asemenea, dacă este posibil, dorim să ne facem șabloanele la fel de prost ca prezentatorii să fie la îndemână în această privință.

În plus, este important să nu vă îndepărtați prea mult de la acțiunile controlerului RESTful. Sigur, din când în când, poate fi logic să existe metode suplimentare acolo, dar, de cele mai multe ori, ar trebui să vă simțiți puțin îngrijorați în jurul lor. Controlorii tind să se îmbogățească atunci când își îngroapă logica de afaceri care de fapt aparține modelelor sau când dezvoltatorii neexperimentați nu folosesc convențiile Rails. 

Tu nu vei fi prima încercare de a reinventa roata, și cu siguranță nu vei fi ultima. Nu vă simțiți rău pentru că, probabil, majoritatea dintre noi am fost acolo, dar ca meseriaș ar trebui să investești timp și efort pentru a cunoaște convențiile, avantajele și limitările cadrelor cu care lucrați - cel puțin în scopuri comerciale unde cineva plătește pentru expertiza ta. Experimentele sunt întotdeauna bine, bineînțeles.

Întrucât controlorii sunt responsabili de fluxul aplicației dvs., precum și de colectarea informațiilor pe care le au nevoie, au deja o responsabilitate destul de importantă. Ei chiar nu au nevoie de o complexitate suplimentară din domeniul modelelor tale. Controlorii lucrează strâns cu vizualizările dvs. pentru a afișa datele furnizate de stratul modelului. Relația lor este mai strânsă decât în ​​cazul modelelor. Stratul de model poate fi dezvoltat mult mai independent de celelalte. Cel mai bun lucru în acest sens este faptul că un strat de controler curat are adesea un efect pozitiv asupra modului în care pot fi văzute vederile dvs..

Ceea ce vreau să spun este că controlorii de grăsimi sunt foarte obișnuiți în landul Rails - în special în rândul începătorilor și dezvoltatorilor fără experiență - și, cu puțină dragoste și îngrijire, acest lucru poate fi optimizat cu ușurință. 

Primul pas este simplu. Întrebați-vă când un controler crește în mărime dacă complexitatea provine din logica de afaceri adăugată. Dacă da, găsiți o modalitate de a vă deplasa la stratul de model în cazul în care aveți avantajul suplimentar de a avea o locuință mai bună pentru testarea codului complex.

Prezentatori

Pentru a urma recomandarea de mai sus privind mutarea logicii controlerului achiziționat în modele, prezentatorii pot fi o tehnică la îndemână. Aceștia pot simula un model, combinând împreună câteva atribute asociate, care pot fi utile pentru a menține controlorii subțiri și sexy. Mai presus de toate, ele sunt, de asemenea, bune pentru păstrarea logicii înșelăciune din opiniile voastre. Destul de bun pentru realizarea unui obiect suplimentar!

Prezentatorii pot "imita" un model care reprezintă statul pe care vizualizarea dvs. are nevoie și care combină atributele care trebuie să se miște prin intermediul controlerului. Ele pot fi mai complexe, dar apoi simt că se deplasează în teritoriul "Decorator". 

Uneori, un controlor este responsabil de crearea simultană a mai multor modele și dorim să evităm manipularea mai multor variabile de instanță acolo. De ce este important acest lucru? Pentru că ne ajută să păstrăm controlul asupra mentenabilității aplicațiilor noastre. Prezentarea agregatelor comportă și atribute, ceea ce face ușor pentru controlorii noștri să se concentreze pe mici, mort-simplu de locuri de muncă-cu un singur obiect. De asemenea, formatarea datelor în vizualizarea dvs. sau alte funcții similare mici sunt locuri de muncă frecvente care apar frecvent. Având acest conținut într-un prezentator este nu numai mare pentru vederi curate, dar și pentru a avea un loc dedicat care face testarea acestui comportament simplu - stratul modelului este mai ușor de testat. Mai mult "bang pentru buck" și tot ce jazz.

Dacă vă întoarceți la modelul de prezentare și găsiți mai multe abordări sau modalități diferite de ao descrie, nu veți fi nebuni. Se pare că nu există prea multe înțelegeri clare cu privire la ceea ce este un prezentator. Ceea ce este cunoscută, totuși, este că se află între straturile MVC. Îl putem folosi pentru a gestiona mai multe obiecte model care trebuie create în același timp. În timp ce combină aceste obiecte, imită un model ActiveRecord. 

Un scenariu frecvent citat este un fel de formă care introduce informații pentru diferite modele diferite, cum ar fi un nou cont de utilizator care are și câmpuri de intrare pentru cărți de credit și adrese sau ceva. Mergând expertul complet prin trecerea prin câteva forme în succesiune nu este atât de diferit. Deoarece aceste părți ale aplicației tind să fie foarte importante, este cu siguranță o idee bună să păstrați ordinea în timp ce aveți cea mai bună opțiune disponibilă pentru testare în același timp. 

Experiența utilizatorului în acest sens este și cheia. În exemplul de mai jos, vrem să creăm o misiune simplă are unul agent și unul intendent. Nu există știință pentru rachete, dar este un bun exemplu pentru a vedea cât de repede lucrurile pot ieși din mână. Controlorul trebuie să jongleze mai multe obiecte pe care vizualizarea are nevoie într-o formă imbricată pentru a lega lucrurile împreună. În curând veți vedea că toate acestea pot fi vindecate cu un "Obiect de formă" frumos, care prezintă obiectele necesare și tese lucrurile împreună într-o singură clasă centrală.

app / modele / mission.rb

clasa Misiune < 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

Vreau să menționez modelele de aici doar de dragul completării în cazul în care nu ați folosit niciodată fields_for înainte-un pic simplificat, dar de lucru. Mai jos este inima chestiunii. 

Prea multe variabile de instanță

app / controlere / missions_controller.rb

clasa 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

În general, este ușor de văzut că acest lucru se îndreaptă în direcția greșită. A fost deja atras un pic de masă și constă doar din nou și crea metode. Nu e bine! Metodele private deja se încarcă prea repede. Având agent_params și quartermaster_params în a MissionsController nu prea sună la tine, sper. O vedere rară, crezi? Mie teama ca nu. "Responsabilitățile unice" în controlerele sunt cu adevărat îndrumări de aur. Veți vedea de ce în doar un minut.

Chiar dacă vă străpungeți ochii, acest lucru arată foarte urât. Și în timpul salvării în crea acțiune, cu validări în vigoare, dacă nu putem salva fiecare obiect din cauza unei greșeli sau a ceva, vom ajunge cu obiecte orfane pe care nimeni nu vrea să le trateze. Hopa! 

Sigur, am putea pune acest lucru într-un tranzacţie bloc, care completează cu succes salvarea numai dacă toate obiectele sunt în ordine, dar acest lucru este un pic ca navigarea împotriva curentului, de asemenea, de ce nu la nivel de model de chestii ca aceasta în controler, într-adevăr? Există mai multe moduri elegante de a prinde un val.

Urmând această cale, punctul de vedere ar avea o însoțire form_for pentru @misiune și suplimentare fields_for pentru @agent și @intendent desigur.

Formă murdară cu obiecte multiple

app / opinii / misiuni / new.html.erb

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

Misiune

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

Agent

<%= 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 %>

Intendent

<%= 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 %>

Sigur, acest lucru funcționează, dar nu aș fi prea încântat să mă împiedic. fields_for sigur este la îndemână și totul, dar manipularea acestui lucru cu OOP este mult mai mult de droguri. Pentru un astfel de caz, un prezentator ne va ajuta, de asemenea, să avem o viziune mai simplă, deoarece formularul se va ocupa doar de un singur obiect. Formarea formei devine astfel inutilă. Apropo, am renunțat la orice împachetări pentru a modela formularul pentru a facilita digerarea vizuală. 

Prezentarea obiectelor de tip

app / opinii / misiuni / new.html.erb

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

Misiune

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

Agent

<%= 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 %>

Intendent

<%= 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 %>

După cum puteți vedea cu ușurință, viziunea noastră a devenit mult mai simplă - nu cuiburi, și acest apartament este mult mai simplu. Partea de care trebuie să fii puțin atentă este următoarea:

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

Trebuie să oferiți form_for cu o cale prin URL-ul astfel încât să poată posta paramurile din acest formular la controlorul său propriu - aici MissionsController. Fără acest argument suplimentar, Rails ar încerca să găsească controlorul pentru obiectul prezentatorului nostru @mission_presenter prin convenții - în acest caz MissionFormPresentersController-și aruncați-o fără unul.

În general, ar trebui să încercăm tot posibilul ca acțiunile controlorului să fie în general la fel de simple ca și manipularea resurselor CRUD - asta este ceea ce un controlor face pentru a-și trăi viața și este cel mai bine echipat să facă fără să distrugă distincțiile MVC. Ca un efect secundar frumos, nivelul de complexitate al controlorilor dvs. va merge și în jos.

app / controlere / missions_controller.rb

clasa 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

Controlerul este, de asemenea, mult mai ușor pentru ochi, nu-i așa? Mult mai curat și destul de mult acțiuni controler standard. Avem de-a face cu un singur obiect care are o slujbă. Instalați un singur obiect, prezentatorul, și îl alimentați paramurile ca de obicei.

Singurul lucru care ma deranjat a fost trimiterea acestei lungi liste de parametri puternici din lista albă. I-am extras într-o metodă numită lista albă, care returnează o matrice cu lista completă de parametri. In caz contrar, mission_params ar fi arătat în felul următor - care se simțea prea urât:

def mission_params params.require (: mission_form_presenter) .permit (: mission_name,: objective,: inamic,: agent_name,: agent_number,: license_to_kill,: quartermaster_name,: quartermaster_number,: hacker,: expertise,: humor) end

Oh, un cuvânt despre : mission_form_presenter argument pentru params.require. Deși am denumit variabila de exemplu pentru prezentator @mission_presenter, când o folosim form_for, Rails se așteaptă ca cheia paragrafului hash să se numească după formarea obiectului - nu după numele dat într-un controler. Am văzut călătorii începători de mai multe ori. Că Rails vă oferă erori criptice într-un astfel de caz nu ajută nici unul. Dacă aveți nevoie de puțină perfecționare pe parami, acesta este un loc bun pentru a sătura:

  • Documentație
  • Videoclip gratuit

În a noastră Misiune model, nu avem nevoie acum accepts_nested_attributes și poate să scape de acel lucru inofensiv, îngrozitor. valideaza metoda este de asemenea irelevantă aici, deoarece adăugăm această responsabilitate obiectului nostru de formă. Același lucru este valabil și pentru validările noastre Agent și Intendent, desigur.

app / modele / mission.rb

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

Incapsularea acestei logici de validare direct pe noul nostru obiect ne ajută să păstrăm lucrurile curate și organizate. În cazurile în care puteți crea și aceste obiecte în mod independent unul de celălalt, validările ar trebui să rămână acolo unde sunt în prezent, desigur. Acest tip de duplicare poate fi, de asemenea, tratată, de exemplu, prin utilizarea validates_with cu o clasă separată pentru validare care moștenește de la ActiveModel :: Validator.

Acum avem un controler slab, cu o singură responsabilitate și o formă plată pentru crearea în paralel a mai multor obiecte. Minunat! Cum am realizat toate astea? Mai jos este prezentatorul care face toată munca - fără a implica această clasă are mult de lucru, totuși. Vrem să avem un model intermediar fără o bază de date care jonglează mai multe obiecte. Aruncați o privire la acest obiect vechi rubinic vechi (PORO).

app / prezentatorii / mission_form_presenter.rb

Clasa MissionFormPresenter include ActiveModel :: Modelul attr_accessor: misiune_name,: obiectiv,: inamic,: agent_name,: agent_number,: license_to_kill,: quartermaster_name,: quartermaster_number,: hacker,: expertiză,: umor validează: mission_name,: agent_name,: quartermaster_name, (mission_attributes) @ mission.create_agent! (agent_attributes) @ mission.create_quartermaster! (quartermaster_attributes) sfârșitul final private def mission_attributes mission_name: mission_name, objective: true def salvează ActiveRecord :: Base.transaction do @ obiectiv, inamic: inamic sfarsit def agent_attributes name: agent_name, numar: agent_number, license_to_kill: license_to_kill sfarsit def quartermaster_attributes name: quartermaster_name, number: quartermaster_number, hacker: hacker;

Cred că este corect să spun că nu este foarte complicat. MissionFormPresenter este un obiect de formă care acum încapsulează ceea ce a făcut regulatorul nostru inutil de grăsime. Ca un bonus, punctul nostru de vedere a devenit plat și simplu. Ce se întâmplă aici este că putem agrega toate informațiile din formularul nostru și apoi vom crea toate obiectele de care avem nevoie secvențial.

Cea mai importantă piesă se întâmplă în noile noastre Salvați metodă. Mai întâi creăm noul Misiune obiect. După aceasta, putem crea cele două obiecte asociate cu acesta: Agent și Intendent. Prin noi are unul și aparține lui asociații, putem face uz de a create_x care se adaptează la numele obiectului asociat. 

De exemplu, dacă folosim has_one: agent, avem un a create_agent metodă. Ușor, nu? (De fapt, avem de asemenea și build_agent metoda.) Am decis să folosesc versiunea cu un bang (!), pentru că ridică un ActiveRecord :: RecordInvalid eroare dacă înregistrarea este nevalidă în timp ce încercați să salvați. Înfășurat în interiorul a tranzacţie bloc, aceste metode de bang au grijă ca niciun obiect orfan să nu fie salvat dacă se valdează o validare. Blocul de tranzacții va reveni dacă ceva nu merge bine în timpul salvării. 

Cum funcționează acest lucru cu atributele, ați putea întreba? Îi întrebăm pe Rails pentru un pic de dragoste via includeți modelul ActiveModel :: Model (API). Acest lucru ne permite să inițializăm obiectele cu un hash de atribute - exact ceea ce facem în controler. După aceea, putem să ne folosim attr_accessor metode pentru a extrage atributele noastre pentru a instantiza obiectele de care avem nevoie.

ActiveModel :: model ne permite să interacționăm cu opiniile și controlorii. Printre celelalte bunuri, puteți folosi și acest lucru pentru validări în astfel de clase. Punerea acestor validări în astfel de obiecte dedicate forme este o idee bună pentru organizare, și vă păstrează, de asemenea, modelele un pic mai tidier. 

Am decis să extrag lista lungă de parametri în metode private care alimentează obiectele create Salvați. Într-un astfel de obiect de prezentare, nu mă îngrijorez prea mult de a avea mai multe metode private situate în jur. De ce nu? Se simte mai curat!

Testarea acestor tipuri de scenarii în care se întâlnesc mai multe modele ar trebui să fie tratată cu cea mai mare grijă - cu cât obiectele în cauză sunt mai simple, cu atât mai bună este experiența de testare. Nu știu rachete, într-adevăr. Prezentatorii operează în favoarea dvs. în acest sens. Având aceste teste potențial legate de controler nu este cel mai bun mod de a aborda acest lucru. Amintiți-vă că testele unității sunt rapide și ieftine.

Un cuvânt de precauție. Nu excesul de prezentatori - nu ar trebui să fie prima ta alegere. De obicei, nevoia de prezentator crește în timp. Pentru mine personal, acestea sunt utilizate cel mai bine atunci când aveți date reprezentate de mai multe modele care trebuie să se unească într-o singură vizualizare. 

Fără un prezentator, este posibil ca mai des decât să pregătiți mai multe variabile de instanță în controlerul dvs. pentru o singură vizualizare. Numai asta le poate face să fie reale, foarte rapide. Un lucru pe care ar trebui să-l consideri și să îl cântărești este că, în timp ce moderatorii adaugă obiecte la baza de cod, pot reduce și numărul de obiecte pe care un controler trebuie să le trateze cu mai puțină complexitate și responsabilități unice. Este probabil o tehnică destul de avansată de a pierde niște grăsimi, dar când doriți să subțiriți, trebuie să puneți în lucru. 

Controlere non-RESTful

Nu încercați să aderați la acțiunile controlerului standard este cel mai probabil o idee proastă. Având tone de metode personalizate controler este un AntiPattern puteți evita destul de ușor. Metode cum ar fi login_user, activate_admin, show_books, și alte afaceri amuzante care stau în pentru nou, crea, spectacol și așa mai departe, ar trebui să vă dați un motiv să vă întrebați și să vă îndoiți de abordarea dvs. Fără a urma o abordare RESTful poate duce cu ușurință la controlori masivi, mai ales pentru că va trebui să lupți cu cadrele sau să reinventezi roata din când în când. 

Pe scurt, nu este o idee bună. De asemenea, de cele mai multe ori, este un simptom al lipsei de experiență sau al neglijenței. Urmărind principiul "Principiul unic al responsabilității" pare să fie foarte greu și în aceste circumstanțe - dar doar o idee educată.

Abordarea resurselor în controlerul dvs. într-o manieră RESTful vă face viața mult mai puțin complicată și aplicațiile mai ușor de întreținut - ceea ce adaugă la stabilitatea generală a aplicației dvs. Gândiți-vă la manipularea resurselor RESTEST din perspectiva ciclului de viață al unui obiect. Creați, actualizați, afișați (single sau colecții), actualizați-le și distrugeți-le. 

În majoritatea cazurilor, acest lucru va face treaba. FYI, nou și Editați | × acțiunile nu sunt într-adevăr parte din REST - ele sunt mai mult ca versiuni diferite ale spectacol acțiune, ajutându-vă să prezentați diferite etape ale ciclului de viață al resurselor. Puneți împreună, de cele mai multe ori, aceste șapte acțiuni controler standard vă oferă tot ce aveți nevoie pentru a vă gestiona resursele în controlorii dvs. Un alt avantaj major este faptul că alți dezvoltatori Rails care lucrează cu codul dvs. vor putea să navigheze controlorii dvs. mult mai rapid.

Urmând acea linie de ajutor RESTful răcoros, aceasta include și modul în care vă numiți controlorii. Numele resursei pe care lucrați trebuie să fie oglindită în obiectul controlerului. 

De exemplu, având a MissionsController care gestionează alte resurse decât @misiune obiecte este un miros că ceva este oprit. Dimensiunea pură a unui controler de multe ori este, de asemenea, un giveaway mort ca REST a fost ignorat. Dacă vă întâlniți controlori mari care implementează tone de metode personalizate care se încalcă cu convențiile, aceasta poate fi o strategie foarte eficientă de a le împărți în mai mulți controlori distinctiv care au responsabilități concentrate - și de fapt gestionează doar o singură resursă, în timp ce aderă la un stil RESTful. Spargeti-le in mod agresiv si veti avea un timp mai usor compunand metodele lor calea Rails.

Rat's Nest Resources

Uitați-vă la următorul exemplu și întrebați-vă ce este în neregulă cu acest lucru:

Nested AgentsController

app / controlere / agents_controller.rb

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

Aici verificăm dacă avem un traseu imbricat care ne oferă ID-ul pentru o posibilă @misiune obiect. Dacă da, vrem să folosim obiectul asociat pentru a obține agenţi din ea. În caz contrar, vom obține o listă cu toți agenții pentru vizualizare. Se pare inofensiv, mai ales pentru că este încă concis, dar este începutul unui cuib de șobolan potențial mult mai mare.

Rutele născute

resurse: agenți resurse: misiuni fac resurse: agenții se termină

Nimic nu este obturat de rutele imbricate aici. În general, nu este nimic în neregulă cu această abordare. Lucrul la care ar trebui să fim atenți este modul în care controlorul se ocupă de această afacere - și, în consecință, cum trebuie să se adapteze vederea. Nu este cu adevărat curat, după cum puteți vedea mai jos.

Vizualizare cu condiție inutilă

app / opinii / agenți / index.html.erb

<% if @mission %> 

Misiune

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

agenţi

    <% @agents.each do |agent| %>
  • Nume: <%= agent.name %>
    Număr: <%= agent.number %>
    Licență pentru a ucide: <%= agent.licence_to_kill %>
    Stare: <%= agent.status %>
  • <% end %>

Poate că nu arăta prea mult, înțeleg. Nivelul de complexitate nu este totuși reală. În afară de aceasta, argumentul se referă mai mult la rezolvarea resurselor într-un mod orientat pe obiecte și la utilizarea Rails în avantajul dvs.. 

Cred că acesta este un pic de margine în ceea ce privește responsabilitățile unice. Nu este chiar o încălcare a acestei idei prea rău, chiar dacă avem un al doilea obiect-@misiune-pentru ca asociația să rămână în jur. Dar din moment ce îl folosim pentru a avea acces la un anumit set de agenți, acest lucru este absolut în regulă.

Ramificarea este partea care este inelegantă și va duce cel mai probabil la decizii de proiectare proaste - atât în ​​vizualizări, cât și în controlori. Crearea a două versiuni de @agents în aceeași metodă este făptuitorul aici. O voi face scurt, acest lucru poate fi foarte rapid. Odată ce ați început să cuibărești resursele de acest gen, șansele sunt bune ca șobolanii noi să se agațe în curând. 

Iar punctul de vedere de mai sus are nevoie de o condiție care se adaptează la situația când ai @agents asociat cu a @misiune. După cum puteți vedea cu ușurință, un pic de sloppiness în controlorul dvs. poate duce la vizualizări umflate care au mai mult cod decât este necesar. Să încercăm o altă abordare. Timp de exterminator! 

Controlere separate

În loc să cuibărească aceste resurse, ar trebui să oferim fiecărei versiuni a acestei resurse propriul controler distinct, focalizat - un controler pentru agenți "simpli", răniți și unul pentru agenții care sunt asociate cu o misiune. Putem realiza acest lucru prin numirea unei persoane sub un a / misiuni pliant. 

app / controlere / misiuni / agents_controller.rb

modul Modele misiune AgentsController < ApplicationController def index @mission = Mission.find(params[:mission_id]) @agents = @mission.agents end end end

Prin împachetarea acestui controler în interiorul unui modul, putem evita AgentsController moșteni de două ori de la ApplicationController. Fără aceasta, am întâmpina o eroare ca aceasta: Imposibil de autoload constantă Misiuni :: AgentsController. Cred că un modul este un preț mic pe care să-l plătești pentru a face ca Rails să fie autoloading fericit. Al doilea AgentsController pot rămâne în același fișier ca înainte. Ea se ocupă acum doar de o posibilă resursă în index-pregătindu-i pe toți agenții fără misiuni care sunt în jur. 

app / controlere / agents_controller.rb

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

Desigur, trebuie să instruim și rutele noastre să caute acest nou controlor de nume, dacă agenții sunt asociați unei misiuni.

resurse: agenți resurse: misiuni fac resurse: agenți, controlor: sfârșitul misiunilor / agenților

După ce am specificat că resursa noastră imbricată are un controler cu nume de nume, suntem cu toții setați. Când facem a rake verificați în terminal, vom vedea că noul nostru controlor este numit și că suntem bine să mergem.

Rute noi

 Prefix Verb URI Regulator de model # Eroare rădăcină GET / agenți # agenți de index GET /agents(.:format) agenți # index POST /agents(.:format) agenți # create new_agent GET /agents/new(.:format) agenți # new edit_agent GET /agents/:id/edit(.format) agenți # agent de editare GET /agents/:id(.:format) agenți # arată PATCH /agents/:id(.:format) agenți # update PUT / agents / : id (.: format) agenți # actualizare DELETE /agents/:id(.:format) agenți # distruge mission_agents GET /missions/:mission_id/agents(.:format) misiuni / agenți # index POST / missions /: mission_id / agenți (.: format) misiuni / agenți # create new_mission_agent GET /missions/:mission_id/agents/new(.:format) misiuni / agenți # new edit_mission_agent GET /missions/:mission_id/agents/:id/edit(.:format ) misiuni / agenți # edita mission_agent GET /missions/:mission_id/agents/:id(.:format) misiuni / agenți # show PATCH /missions/:mission_id/agents/:id(.:format) misiuni / agenți # update PUT /missions/:mission_id/agents/:id(.:format) misiuni / agenți # update DELETE / missions: mission_id / agents / : id (.: format) misiuni / agenți # distruge

Resursa noastră imbricată pentru agenţi este acum redirecționată corespunzător controlere / misiuni / agents_controller.rb și fiecare acțiune poate avea grijă de agenții care fac parte dintr-o misiune. Din motive de exhaustivitate, să aruncăm o privire și la opiniile noastre finale:

Agenți cu misiune

app / opinii / misiuni / agenți / index.html.erb 

Misiune

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

agenţi

    <% @agents.each do |agent| %>
  • Nume: <%= agent.name %>
    Număr: <%= agent.number %>
    Licență pentru a ucide: <%= agent.licence_to_kill %>
  • <% end %>

Agenți fără misiune

app / opinii / agenți / index.html

agenţi

    <% @agents.each do |agent| %>
  • Nume: <%= agent.name %>
    Număr: <%= agent.number %>
    Licență pentru a ucide: <%= agent.licence_to_kill %>
  • <% end %>

Ei bine, hai să scăpăm de acel pic de duplicare în cazul în care vom repeta @agents de asemenea. Am creat un parțial pentru a da o listă de agenți și ao pune într-un nou impartit directorul de sub vizualizari

app / opinii / partajat / _agents.html.erb

agenţi

    <% @agents.each do |agent| %>
  • Nume: <%= agent.name %>
    Număr: <%= agent.number %>
    Licență pentru a ucide: <%= agent.licence_to_kill %>
  • <% end %>

Nimic nou sau surprinzator aici, dar vederile noastre sunt acum mai uscate.

Agenți cu misiune

app / opinii / misiuni / agenți / index.html.erb 

Misiune

<%= @mission.mission_
Cod