def spectre_member_name

Anti-cosa? Probabilmente sembra molto più complicato di quello che è. Negli ultimi due decenni, i programmatori sono stati in grado di identificare un'utile selezione di modelli di "progettazione" che si sono spesso verificati nelle loro soluzioni di codice. Mentre risolvevano problemi simili, erano in grado di "classificare" soluzioni che impedivano loro di reinventare la ruota tutto il tempo. È importante notare che questi pattern dovrebbero essere visti più come scoperte delle invenzioni di un gruppo di sviluppatori avanzati.

Se questo è piuttosto nuovo per te e ti vedi come se fossi più da principiante di tutto ciò che è Ruby / Rails, allora questo è esattamente scritto per te. Penso che sia la cosa migliore se la pensi come un rapido e magro tuffo in un argomento molto più profondo la cui maestria non avverrà da un giorno all'altro. Ciononostante, credo fermamente che iniziare a farlo presto servirà enormemente i principianti ei loro mentori.

AntiPattern, come suggerisce il nome, rappresenta praticamente l'opposto dei modelli. Sono scoperte di soluzioni a problemi che dovresti assolutamente evitare. Spesso rappresentano il lavoro di programmatori inesperti che non sanno ancora cosa non sanno ancora. Peggio ancora, potrebbero essere l'output di una persona pigra che ignora le best practice e gli strumenti framework senza una buona ragione - o pensano di non averne bisogno. Ciò che potrebbero sperare di ottenere in termini di risparmio all'inizio, eliminando soluzioni rapide, pigre o sporche, li perseguiterà o li scuserà successivamente nel ciclo di vita del progetto.

Non sottovalutare le implicazioni di queste decisioni sbagliate: ti perseguiteranno, qualunque cosa accada.

Temi

  • Modelli grassi
  • Suite di test mancante
  • Modelli voyeuristici
  • Legge di Demetra
  • Spaghetti SQL

Modelli grassi

Sono sicuro che hai ascoltato i "Fat models, skinny controller" un sacco di volte quando hai iniziato con Rails. OK, ora dimenticalo! Certo, la logica del business deve essere risolta nel livello del modello, ma non dovresti sentirti incline a inserire tutto ciò che c'è dentro senza senso solo per evitare di attraversare le linee nel territorio del controller.

Ecco un nuovo obiettivo che dovresti mirare: "Modelli magri, controller magri". Potresti chiedere: "Bene, come dovremmo organizzare il codice per ottenerlo? Dopo tutto, è un gioco a somma zero?". Un buon punto! Il nome del gioco è la composizione e Ruby è ben attrezzato per darti molte opzioni per evitare l'obesità del modello.

Nella maggior parte delle applicazioni web (Rails) che sono supportate dal database, la maggior parte della vostra attenzione e del vostro lavoro saranno centrate sul livello del modello, dato che lavorate con designer competenti che sono in grado di implementare le proprie cose nella vista, intendo. I tuoi modelli avranno di per sé più "gravità" e attraggono più complessità.

La domanda è esattamente come intendi gestire questa complessità. Active Record ti dà sicuramente un sacco di corda per impiccarti mentre rendi la tua vita incredibilmente facile. È un approccio allettante per progettare il tuo livello di modello semplicemente seguendo il percorso della massima praticità immediata. Ciononostante, un'architettura a prova di futuro richiede molta più considerazione che coltivare classi enormi e riempire tutto in oggetti Active Record.

Il vero problema di cui ti occupi qui è la complessità, inutilmente così, direi. Le classi che accumulano tonnellate di codice diventano complesse solo per la loro dimensione. Sono più difficili da mantenere, difficili da analizzare e da capire, e sempre più difficile da cambiare perché la loro composizione probabilmente manca di disaccoppiamento. Questi modelli spesso superano la loro capacità raccomandata di gestire una singola responsabilità e sono piuttosto dappertutto. Nel peggiore dei casi, diventano come camion della spazzatura, gestendo tutta la spazzatura che è pigramente lanciata contro di loro.

Possiamo fare di meglio! Se pensi che la complessità non sia un grosso problema, dopo tutto, sei speciale, intelligente e pensa di nuovo! La complessità è il più famoso serial killer del progetto, non il tuo amichevole quartiere "Dark Defender".

I "modelli Skinnier" raggiungono una persona avanzata nel settore della codifica (probabilmente molte più professioni rispetto al codice e al design) e ciò a cui tutti dovremmo sforzarci assolutamente: la semplicità! O almeno di più, che è un giusto compromesso se la complessità è difficile da sradicare.

Quali strumenti offre Ruby per semplificarci la vita a tale riguardo e permetterci di eliminare il grasso dai nostri modelli? Semplice, altre classi e moduli. Identificando un codice coerente che potresti estrarre in un altro oggetto e quindi creare un livello di modello costituito da agenti di dimensioni ragionevoli che hanno le loro responsabilità uniche e distintive.

Pensaci in termini di un artista di talento. Nella vita reale, una tale persona potrebbe essere in grado di rapare, spezzare, scrivere testi e produrre le proprie melodie. Nella programmazione preferisci le dinamiche di una band, qui con almeno quattro membri distintivi, in cui ogni persona è responsabile del minor numero possibile di cose. Vuoi costruire un'orchestra di classi in grado di gestire la complessità del compositore, non una classe di maestro genomico micromanaging di tutti i mestieri.

Diamo un'occhiata a un esempio di un modello di grasso e giocare con un paio di opzioni per gestire la sua obesità. L'esempio è un manichino, naturalmente, e raccontando questa piccola favola spero che sarà più facile da digerire e seguire per i principianti.

Abbiamo una classe Spectre che ha troppe responsabilità e pertanto è cresciuta inutilmente. Oltre a questi metodi, penso che sia facile immaginare che un tale campione abbia già accumulato molte altre cose come ben rappresentato dai tre puntini. Spectre è sulla buona strada per diventare una classe di Dio. (Le probabilità sono piuttosto basse per formulare in modo ragionevole una simile frase in qualunque momento presto!)

"Spettrale di classe rubino < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations

...

def turn_mi6_agent (enemy_agent) mette "MI6 agent # enemy_agent.name girato in Spectre" fine

def turn_cia_agent (enemy_agent) mette "L'agente CIA # enemy_agent.name consegnato a Spectre" fine

def turn_mossad_agent (enemy_agent) mette "L'agente del Mossad # enemy_agent.name consegnato a Spectre" fine

def kill_double_o_seven (spectre_agent) spectre_agent.kill_james_bond end

def dispose_of_cabinet_member (number) spectre_member = SpectreMember.find_by_id (numero)

mette "Un certo colpevole ha fallito l'integrità assoluta di questa fraternità.L'atto appropriato è di fumare numero # numero sulla sua sedia.I suoi servizi non saranno molto persi" spectre_member.die fine 

def print_assignment (operation) mette "L'operazione # operation.name ha come obiettivo # operation.objective." fine

privato

def enemy_agent #clever code end

def spectre_agent #clever code end

def operation #clever code end

...

fine

"

Spectre trasforma vari tipi di agenti nemici, i delegati uccidono 007, griglia i membri del gabinetto di Spectre quando falliscono, e stampa anche i compiti operativi. Un chiaro caso di microgestione e sicuramente una violazione del "principio di responsabilità unica". Anche i metodi privati ​​si accumulano velocemente.

Questa classe non ha bisogno di sapere la maggior parte delle cose che sono attualmente in esso. Divideremo questa funzionalità in un paio di classi e vedremo se la complessità di avere un paio di altre classi / oggetti vale la liposuzione.

"rubino

classe Spectre < ActiveRecord::Base has_many :spectre_members has_many :spectre_agents has_many :enemy_agents has_many :operations

...

def turn_enemy_agent Interrogator.new (enemy_agent) .turn end

privato

def enemy_agent self.enemy_agents.last end end

class Interrogator attr_reader: enemy_agent

def initialize (enemy_agent) @enemy_agent = enemy_agent end

def turn enemy_agent.turn end end

classe EnemyAgent < ActiveRecord::Base belongs_to :spectre belongs_to :agency

def turn puts 'Dopo estesi lavaggi del cervello, torture e orde di denaro ...' end end

classe MI6Agent < EnemyAgent def turn super puts “MI6 agent #name turned over to Spectre” end end

classe CiaAgent < EnemyAgent def turn super puts “CIA agent #name turned over to Spectre” end end

classe MossadAgent < EnemyAgent def turn super puts “Mossad agent #name turned over to Spectre” end end

classe NumberOne < ActiveRecord::Base def dispose_of_cabinet_member(number) spectre_member = SpectreMember.find_by_id(number)

"Un certo colpevole ha fallito nell'assoluta integrità di questa fraternità, l'atto appropriato è quello di fumare il numero # numero sulla sua sedia.I suoi servizi non saranno persi" spectre_member.die end end 

operazione di classe < ActiveRecord::Base has_many :spectre_agents belongs_to :spectre

def print_assignment mette "L'operazione # nome l'obiettivo è # obiettivo." fine

classe SpectreAgent < ActiveRecord::Base belongs_to :operation belongs_to :spectre

def kill_james_bond mette "Mr. Bond, mi aspetto che tu muoia! "Fine

classe SpectreMember < ActiveRecord::Base belongs_to :spectre

def muore mette "Nooo, nooo, non era meeeeeeeee! ZCHUNK! "End-end

"

Penso che la parte più importante a cui dovresti prestare attenzione è come abbiamo usato una semplice classe di Ruby come Interrogatorio per gestire il passaggio di agenti da diverse agenzie. Gli esempi del mondo reale potrebbero rappresentare un convertitore che, ad esempio, trasforma un documento HTML in un pdf e viceversa. Se non hai bisogno della piena funzionalità delle classi di Active Record, perché usarle se anche una semplice classe Ruby può fare il trucco? Un po 'meno corda con cui attaccarci.

La classe Spectre lascia la cattiva attività di trasformare agenti in Interrogatorio classe e solo delegati ad esso. Questo ha ora la sola responsabilità della tortura e del lavaggio del cervello degli agenti catturati.

Fin qui tutto bene. Ma perché abbiamo creato classi separate per ogni agente? Semplice. Invece di estrarre direttamente i vari metodi di turn come turn_mi6_agent oltre a Interrogatorio, abbiamo dato loro una casa migliore nella loro rispettiva classe.

Di conseguenza, possiamo fare un uso efficace del polimorfismo e non preoccuparci dei casi individuali per agenti di svolta. Diciamo solo a questi diversi oggetti agente di girare, e ognuno di loro sa cosa fare. Il Interrogatorio non ha bisogno di conoscere le specifiche su come ogni agente gira.

Dal momento che tutti questi agenti sono oggetti Active Record, ne abbiamo creato uno generico, EnemyAgent, questo ha un senso generale di ciò che significa trasformare un agente e incapsulare quel bit per tutti gli agenti in un unico posto sottoclassandolo. Usiamo questa eredità fornendo il turno metodi dei vari agenti con super, e quindi abbiamo accesso al business del lavaggio del cervello e della tortura, senza duplicazioni. Le singole responsabilità e la duplicazione non sono un buon punto di partenza per andare avanti.

Le altre classi di Active Record assumono varie responsabilità che Spectre non ha bisogno di preoccuparsi. "Numero uno" di solito fa la griglia dei membri del gabinetto dello Spettro falliti, quindi perché non lasciare che un oggetto dedicato maneggi la scarica elettrica? D'altra parte, i membri dello Spectre falliti sanno come morire da soli quando vengono fumati nella loro sedia da Numero uno. operazione ora stampa anche i suoi compiti - non c'è bisogno di sprecare il tempo di Spectre con le arachidi del genere.

Ultimo ma non meno importante, uccidere James Bond è solitamente tentato da un agente sul campo, quindi kill_james_bond è ora un metodo su SpectreAgent. Goldfinger avrebbe dovuto gestirlo in modo diverso, ovviamente - devo giocare con quel coso laser se ne hai uno, immagino.

Come puoi vedere chiaramente, ora abbiamo dieci classi in cui prima ne avevamo solo una. Non è troppo? Può essere, di sicuro. È un problema che dovrai lottare con la maggior parte del tempo quando dividi tali responsabilità. Puoi sicuramente strafare. Ma guardare questo da un'altra angolazione potrebbe aiutare:

  • Abbiamo separato le preoccupazioni? Assolutamente!
  • Abbiamo classi leggere e magre che sono più adatte a gestire responsabilità singolari? Abbastanza sicuro!
  • Diciamo una "storia", stiamo dipingendo un'immagine più chiara di chi è coinvolto ed è responsabile di certe azioni? lo spero!
  • È più facile digerire ciò che ogni classe sta facendo? Di sicuro!
  • Abbiamo ridotto il numero di metodi privati? Sì!
  • Questo rappresenta una migliore qualità della programmazione orientata agli oggetti? Dal momento che abbiamo usato la composizione e ci siamo riferiti all'eredità solo dove necessario per la creazione di questi oggetti, puoi scommettere!
  • Ti sembra più pulito? sì!
  • Siamo meglio equipaggiati per cambiare il nostro codice senza fare casino? Cosa certa!
  • Ne valeva la pena? Cosa pensi?

Non sto insinuando che queste domande debbano essere spuntate dal tuo elenco ogni volta, ma queste sono le cose che probabilmente dovresti iniziare a pormi mentre snellisci i tuoi modelli.

La progettazione di modelli skinny può essere difficile, ma è una misura essenziale per mantenere le tue applicazioni sane e agili. Anche questi non sono gli unici modi costruttivi per affrontare i modelli grassi, ma sono un buon inizio, soprattutto per i neofiti.

Suite di test mancante

Questo è probabilmente l'AntiPattern più ovvio. Provenendo dal lato guidato dal test, toccare un'app matura che non ha copertura di test può essere una delle esperienze più dolorose da affrontare. Se vuoi odiare il mondo e la tua stessa professione più di ogni altra cosa, passa solo sei mesi a questo progetto e imparerai quanto di un misantropo è potenzialmente in te. Sto scherzando, certo, ma dubito che ti renderà più felice e che vorresti farlo di nuovo, sempre. Forse farà anche una settimana. Sono abbastanza sicuro che la parola tortura ti verrà in mente più spesso di quanto pensi.

Se il test non è stato parte del processo fino a quel momento e quel tipo di dolore è normale per il tuo lavoro, forse dovresti considerare che il test non è poi così male, né è un tuo nemico. Quando i tuoi livelli di gioia legati al codice sono più o meno costantemente sopra lo zero e puoi cambiare il codice senza paura, allora la qualità complessiva del tuo lavoro sarà molto più alta rispetto all'output che è contaminato da ansia e sofferenza.

Sto sopravvalutando? Io davvero non la penso così! Vuoi avere una copertura di test molto ampia, non solo perché è un ottimo strumento di progettazione per scrivere solo il codice che ti serve realmente, ma anche perché in futuro dovrai modificare il tuo codice. Sarai molto meglio equipaggiato per interagire con il tuo codebase e molto più sicuro di te, se disponi di un'imbracatura di test che aiuti e guidi i refactoring, la manutenzione e le estensioni. Si verificheranno sicuramente lungo la strada, senza dubbi a riguardo.

Questo è anche il punto in cui una suite di test inizia a ripagare il secondo giro di dividendi, perché l'aumento della velocità con cui è possibile apportare in modo sicuro questi cambiamenti di qualità non può essere raggiunto da una lunga distanza in app create da persone che pensano di scrivere test è senza senso o richiede troppo tempo.

Modelli voyeuristici

Questi sono modelli che sono super ficcanaso e vogliono raccogliere troppe informazioni su altri oggetti o modelli. Ciò è in netto contrasto con una delle idee più fondamentali nell'incapsulazione di programmazione orientata agli oggetti. Vogliamo piuttosto impegnarci per classi e modelli autosufficienti che gestiscano il più possibile i loro affari interni. In termini di concetti di programmazione, questi modelli voyeuristici violano fondamentalmente il "Principio di minima conoscenza", alias la "Legge di Demetra", in qualunque modo tu voglia pronunciarlo.

Legge di Demetra

Perché questo è un problema? È una forma di duplicazione - una sottile - e porta anche a un codice che è molto più fragile del previsto.

The Law of Demeter è praticamente l'odore di codice più affidabile che puoi sempre attaccare senza preoccuparti dei possibili svantaggi.

Immagino che chiamare questa "legge" non sia così pretenziosa come potrebbe sembrare all'inizio. Scava in questo odore, perché ne avrai bisogno molto nei tuoi progetti. In pratica afferma che in termini di oggetti, puoi chiamare metodi sull'amico del tuo oggetto ma non sull'amico del tuo amico.

Questo è un modo comune per spiegarlo, e tutto si riduce a utilizzare non più di un singolo punto per le chiamate al metodo. A proposito, è assolutamente bene usare più punti o chiamate di metodo quando si ha a che fare con un singolo oggetto che non cerca di raggiungere più lontano. Qualcosa di simile a @ weapons.find_by_name ('Poison dart'). formula va bene I cercatori possono accumulare qualche punto a volte. Incapsularli in metodi dedicati è comunque una buona idea.

Legge sulle violazioni di Demetra

Diamo un'occhiata a un paio di esempi negativi delle classi sopra:

"rubino

@ operation.spectre_agents.first.kill_james_bond

@ spectre.operations.last.spectre_agents.first.name

@ spectre.enemy_agents.last.agency.name

"

Per farcela, ecco alcuni più romanzati:

"rubino

@ quartermaster.gizmos.non_lethal.favorite

@ mi6.operation.agent.favorite_weapon

@ mission.agent.name

"

Banane, giusto? Non sembra buono, vero? Come puoi vedere, questo metodo richiama troppo l'attenzione sul business di altri oggetti. La conseguenza negativa più importante ed evidente è il cambiamento di un gruppo di questi metodi che invoca tutto il posto se la struttura di questi oggetti deve cambiare, cosa che alla fine, poiché l'unica costante nello sviluppo del software è il cambiamento. Inoltre, sembra davvero brutto, non facile per gli occhi. Quando non sai che questo è un approccio problematico, Rails ti consente di portarlo a termine molto lontano, senza urlare contro di te. Un sacco di corda, ricorda?

Quindi cosa possiamo fare a riguardo? Dopotutto, vogliamo ottenere quell'informazione in qualche modo. Per prima cosa possiamo comporre i nostri oggetti per soddisfare le nostre esigenze e possiamo fare un uso intelligente della delega per mantenere i nostri modelli snelli allo stesso tempo. Entriamo nel codice per mostrarti cosa intendo.

"rubino

classe SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents

...

fine

operazione di classe < ActiveRecord::Base belongs_to :spectre_member

...

fine

classe SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

...

fine

@ spectre_member.spectre_agents.all @ spectre_member.operations.last.print_assignment @ spectre_member.spectre_agents.find_by_id (1) .name

@ operation.spectre_member.name @ operation.spectre_member.number @ operation.spectre_member.spectre_agents.first.name

@ spectre_agent.spectre_member.number

"

"rubino

classe SpectreMember < ActiveRecord::Base has_many :operations has_many :spectre_agents

...

def list_of_agents spectre_agents.all end

def print_operation_details operation = Operation.last operation.print_operation_details end end

operazione di classe < ActiveRecord::Base belongs_to :spectre_member

...

def spectre_member_name spectre_member.name end

def spectre_member_number spectre_member.number end

def print_operation_details puts "L'obiettivo di questa operazione è # obiettivo. Il target è # target "end-end

classe SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

...

def superior_in_charge inserisce il capo "Il mio capo è numero # spectre_member.number"

@ spectre_member.list_of_agents @ spectre_member.print_operation_details

@ operation.spectre_member_name @ operation.spectre_member_number

@ spectre_agent.superior_in_charge

"

Questo è sicuramente un passo nella giusta direzione. Come puoi vedere, abbiamo impacchettato le informazioni che volevamo acquisire in un sacco di metodi wrapper. Invece di raggiungere direttamente molti oggetti, abbiamo estrapolato questi ponti e li abbiamo lasciati ai rispettivi modelli per parlare ai loro amici delle informazioni di cui abbiamo bisogno.

Il rovescio della medaglia di questo approccio sta avendo tutti questi metodi di wrapper extra in giro. A volte va bene, ma vogliamo davvero evitare di mantenere questi metodi in un sacco di posti se un oggetto cambia.

Se possibile, il posto dedicato a loro per cambiare è sul loro oggetto - e sul loro oggetto da solo. Anche gli oggetti inquinanti con metodi che hanno poco a che fare con il loro stesso modello è qualcosa a cui prestare attenzione poiché questo è sempre un potenziale pericolo per abbattere le singole responsabilità.

Possiamo fare meglio di così. Dove possibile, deleghiamo le chiamate al metodo direttamente ai loro oggetti incaricati e cerchiamo di ridurre i metodi del wrapper il più possibile. Rails sa cosa ci serve e ci fornisce il pratico delegare metodo di classe per dire agli amici del nostro oggetto quali metodi abbiamo bisogno di chiamare.

Esaminiamo qualcosa dell'esempio di codice precedente e vediamo dove possiamo fare un uso corretto della delega.

"rubino

operazione di classe < ActiveRecord::Base belongs_to :spectre_member

delegato: nome,: numero, a:: spectre_member, prefisso: true

...

def spectre_member_name

# spectre_member.name # end

def spectre_member_number

# spectre_member.number # end

...

fine

@ operation.spectre_member_name @ operation.spectre_member_number

classe SpectreAgent < ActiveRecord::Base belongs_to :spectre_member

delegato: numero, a:: spectre_member, prefisso: true

...

def superior_in_charge mette fine "My boss is number # spectre_member_number"

...

fine

"

Come puoi vedere, possiamo semplificare un po 'le cose usando la delega dei metodi. Ci siamo liberati di Operazione # spectre_member_name e Operazione # spectre_member_number completamente, e SpectreAgent non ha bisogno di chiamare numero sopra spectre_member più-numero è delegato di nuovo direttamente alla sua classe "origine" SpectreMember.

Nel caso questo sia un po 'di confusione all'inizio, come funziona esattamente? Dì al delegato quale : method_name dovrebbe delegare a: quale :nome della classe (anche i nomi di più metodi vanno bene). Il prefisso: vero la parte è facoltativa.

Nel nostro caso, ha preceduto il nome della classe snake-cased della classe ricevente prima del nome del metodo e ci ha permesso di chiamare operation.spectre_member_name invece del potenzialmente ambiguo operation.name-se non avessimo usato l'opzione prefisso. Funziona davvero bene con appartiene a e Ha uno associazioni.

Sul ha molti lato delle cose, però, la musica si fermerà e ti troverai nei guai. Queste associazioni ti forniscono un proxy di raccolta che genererà NameErrors o NoMethodErrors quando deleghi i metodi a queste "raccolte".

Spaghetti SQL

Per completare questo capitolo sui modelli AntiPattern in Rails, vorrei dedicare un po 'di tempo a cosa evitare quando SQL è coinvolto. Le associazioni di record attivi forniscono opzioni che rendono le vostre vite sostanzialmente più semplici quando siete consapevoli di cosa dovreste evitare. I metodi del Finder sono un argomento completo per conto loro e non li copriremo in profondità - ma volevo menzionare alcune tecniche comuni che ti aiutano anche quando scrivi cose molto semplici.

Le cose che dovremmo preoccuparci di echeggiare la maggior parte di ciò che abbiamo imparato finora. Vogliamo avere metodi intenzionalmente rivelatori, semplici e ragionevolmente nominati per trovare cose nei nostri modelli. Entriamo nel codice.

"rubino

operazione di classe < ActiveRecord::Base

has_many: agenti

...

fine

agente di classe < ActiveRecord::Base

appartiene a: operazione

...

fine

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = Agent.where (operation_id: @ operation.id, licence_to_kill: true) end end

"

Sembra innocuo, no? Stiamo solo cercando un gruppo di agenti che hanno la licenza per uccidere per la nostra pagina di operazioni. Pensa di nuovo. Perché dovrebbe OperationsController scavare negli interni di Agente? Inoltre, questo è davvero il meglio che possiamo fare per incapsulare un cercatore su Agente?

Se stai pensando che potresti aggiungere un metodo di classe come Agent.find_licence_to_kill_agents che incapsula la logica del cercatore, stai sicuramente facendo un passo nella giusta direzione, non abbastanza, però.

"rubino

agente di classe < ActiveRecord::Base

appartiene a: operazione

def self.find_licence_to_kill_agents (operation) where (operation_id: operation.id, licence_to_kill: true) end ...

fine

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = Agent.find_licence_to_kill_agents (@operation) end end

"

Dobbiamo essere un po 'più impegnati di così. Prima di tutto, non stiamo usando le associazioni a nostro vantaggio, e l'incapsulamento è anche subottimale. Associazioni come ha molti vieni con il vantaggio che possiamo aggiungere alla matrice proxy che ci viene restituita. Avremmo potuto farlo invece:

"rubino

operazione di classe < ActiveRecord::Base

has_many: agenti

def find_licence_to_kill_agents self.agents.where (licence_to_kill: true) end ...

fine

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = @ operation.find_licence_to_kill_agents end end

"

Questo funziona, di sicuro, ma è anche solo un altro piccolo passo nella giusta direzione. Sì, il controller è un po 'migliore, e facciamo un buon uso delle associazioni di modelli, ma dovresti comunque essere sospettosi sul perché operazione si occupa dell'implementazione di trovare un certo tipo di Agente. Questa responsabilità appartiene al Agente modello stesso.

Gli ambiti nominati sono molto utili con questo. Gli ambiti definiscono metodi chainable-important-class per i tuoi modelli e quindi ti permettono di specificare query utili che puoi usare come chiamate di metodo addizionali sulle associazioni di modelli. I seguenti due approcci per l'ambito Agente sono indifferenti.

"rubino

agente di classe < ActiveRecord::Base belongs_to :operation

scope: licenced_to_kill, -> where (licence_to_kill: true) end

agente di classe < ActiveRecord::Base belongs_to :operation

def self.licenced_to_kill where (licence_to_kill: true) end end

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @agents = @ operation.agents.licenced_to_kill end end

"

Questo è molto meglio. Nel caso in cui la sintassi degli scope sia nuova per te, sono solo dei lambdas (pugnalati) - non molto importanti per esaminarli subito, a proposito - e sono il modo corretto per chiamare gli scope poiché Rails 4. Agente ora è incaricato di gestire i propri parametri di ricerca e le associazioni possono semplicemente capire cosa devono trovare.

Questo approccio consente di raggiungere le query come singole chiamate SQL. Mi piace usare personalmente scopo per la sua chiarezza. Gli scope sono anche molto utili per incatenare all'interno di metodi ben definiti di ricerca, in questo modo aumentano la possibilità di riutilizzare codice e codice di DRY-ing. Diciamo che abbiamo qualcosa di un po 'più coinvolto:

"rubino

agente di classe < ActiveRecord::Base belongs_to :operation

scope: licenced_to_kill, -> where (licence_to_kill: true) scope: womanizer, -> where (womanizer: true) scope: bond, -> where (nome: 'James Bond') scope: giocatore d'azzardo, - > where (gambler: true) end

"

Ora possiamo utilizzare tutti questi ambiti per creare query più complesse.

"rubino

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.licenced_to_kill end

def show @operation = Operation.find (params [: id]) @bond = @ operation.agents.womanizer.gambler.licenced_to_kill end

… fine

"

Certo, funziona, ma mi piacerebbe suggerirti di fare un passo avanti.

"rubino

agente di classe < ActiveRecord::Base belongs_to :operation

scope: licenced_to_kill, -> where (licence_to_kill: true) scope: womanizer, -> where (womanizer: true) scope: bond, -> where (nome: 'James Bond') scope: giocatore d'azzardo, - > where (giocatore d'azzardo: vero)

def self.find_licenced_to_kill licenced_to_kill end

def self.find_licenced_to_kill_womanizer womanizer.licenced_to_kill end

def self.find_gambling_womanizer gambler.womanizer end

...

fine

class OperationsController < ApplicationController

def index @operation = Operation.find (params [: id]) @double_o_agents = @ operation.agents.find_licenced_to_kill end

def show @operation = Operation.find (params [: id]) @bond = @ operation.agents.find_licenced_to_kill_womanizer #or @bond = @ operation.agents.bond end

...

fine

"

Come puoi vedere, attraverso questo approccio raccogliamo i vantaggi di un'appropriata incapsulamento, associazioni di modelli, riutilizzo del codice e denominazione espressiva dei metodi, e tutto ciò mentre facciamo query SQL singole. Basta codice spaghetti, fantastico!

Se sei preoccupato di violare la legge di Demeter, sarai felice di sapere che poiché non stiamo aggiungendo punti raggiungendo il modello associato ma incatenandoli solo al loro oggetto, non commettiamo alcun crimine Demeter.

Pensieri finali

Dal punto di vista di un principiante, penso che tu abbia imparato molto sulla migliore gestione dei modelli Rails e su come modellarli in modo più robusto senza chiamare un boia.

Non fatevi ingannare, però, nel pensare che non c'è molto altro da imparare su questo particolare argomento. Ti ho presentato alcuni AntiPattern che ritengo che i neofiti siano in grado di capire e gestire facilmente per proteggersi da subito. Se non sai cosa non sai, è disponibile un sacco di corda per avvolgerti intorno al collo.

Anche se questo è stato un inizio solido in questo argomento, non ci sono solo più aspetti di AntiPattern nei modelli Rails, ma anche altre sfumature che dovrai esplorare. Queste erano le basi - molto importanti e importanti - e dovresti sentirti realizzato per un po 'che non hai aspettato molto più tardi nella tua carriera per capirle.