În această ultimă piesă, vom arăta un pic mai adânc în întrebări și vom juca cu câteva scenarii mai avansate. Vom acoperi relațiile dintre modelele Active Record un pic mai mult în acest articol, dar voi sta departe de exemple care ar putea fi prea confuze pentru programarea începătorilor. Înainte de a vă deplasa înainte, lucruri precum exemplul de mai jos nu trebuie să provoace confuzie:
Mission.last.agents.where (nume: "James Bond")
Dacă sunteți nou în interogările Active Record și SQL, vă recomandăm să aruncați o privire la cele două articole anterioare înainte de a continua. Aceasta ar putea fi greu de înghițit fără cunoștințele pe care le-am construit până acum. Până la tine, desigur. Pe de altă parte, acest articol nu va fi la fel de lung ca și celelalte, dacă doriți doar să vă uitați la aceste cazuri de utilizare ușoară. Hai să intrăm!
Să reiteream. Putem interoga imediat modelele Active Record, dar asociațiile sunt, de asemenea, un joc echitabil pentru întrebări - și putem lanțuri toate aceste lucruri. Până acum, bine. Putem să găsim pachete de identificatori în domenii de utilizare reutilizabile și în modelele dvs., și am menționat în scurt timp similitudinea lor cu metodele de clasă.
Agent de clasă < ActiveRecord::Base belongs_to :mission scope :find_bond, -> unde (nume: "James Bond") domeniul de aplicare: licenced_to_kill, -> where (license_to_kill: true) domeniu: femeie, adevărat) end # => Agent.find_bond # => Agent.licenced_to_kill # => Agent.womanizer # => Agent.gambler # => Mission.last.agents.find_bond # => Mission.last.agents.licenced_to_kill # > Mission.last.agents.womanizer # => Mission.last.agents.gambler # => Agent.licenced_to_kill.womanizer.gambler # => Mission.last.agents.womanizer.gambler.licenced_to_kill
Deci, puteți să le împachetați și în metodele proprii de clasă și să le faceți cu ele. Scopes nu sunt iffy sau altceva, cred că-deși oamenii le menționează ca fiind un pic de magie aici și acolo-dar, deoarece metodele de clasă atinge același lucru, aș opta pentru asta.
Agent de clasă < ActiveRecord::Base belongs_to :mission def self.find_bond where(name: 'James Bond') end def self.licenced_to_kill where(licence_to_kill: true) end def self.womanizer where(womanizer: true) end def self.gambler where(gambler: true) end end # => Agent.find_bond # => Agent.licenced_to_kill # => Agent.womanizer # => Agent.gambler # => Mission.last.agents.find_bond # => Mission.last.agents.licenced_to_kill # => Mission.last.agents. femeie # => Mission.last.agents.gambler # => Agent.licenced_to_kill.womanizer.gambler # => Mission.last.agents.womanizer.gambler.licenced_to_kill
Aceste metode de clasă citesc exact același lucru și nu trebuie să înjunghiați pe nimeni cu o lambda. Orice lucreaza cel mai bine pentru tine sau pentru echipa ta; depinde de tine ce API doriți să utilizați. Doar nu amestecați-le și le potriviți-stick cu o singură alegere! Ambele versiuni vă permit să legeți cu ușurință aceste metode într-o altă metodă de clasă, de exemplu:
Agent de clasă < ActiveRecord::Base belongs_to :mission scope :licenced_to_kill, -> where (license_to_kill: true) domeniu: feminin, -> unde (femeie: adevărat) def self.find_licenced_to_kill_womanizer womanizer.licenced_to_kill end end # => Agent.find_licenced_to_kill_womanizer # => Mission.last.agents.find_licenced_to_kill_womanizer
Agent de clasă < ActiveRecord::Base belongs_to :mission def self.licenced_to_kill where(licence_to_kill: true) end def self.womanizer where(womanizer: true) end def self.find_licenced_to_kill_womanizer womanizer.licenced_to_kill end end # => Agent.find_licenced_to_kill_womanizer # => Mission.last.agents.find_licenced_to_kill_womanizer
Hai să facem un mic pas mai departe - stai cu mine. Putem folosi o lambda în asociații pentru a defini un anumit domeniu. Pare puțin ciudat la început, dar ele pot fi destul de la îndemână. Acest lucru face posibilă numirea acestor lambe-uri la asociațiile dvs..
Acest lucru este destul de rece și foarte ușor de citit, cu metode mai scurte de înlănțuire continuă. Feriți-vă de cuplarea acestor modele prea strânse, totuși.
clasa Misiune < ActiveRecord::Base has_many :double_o_agents, -> where (license_to_kill: true), class_name: "Agent" end # => Mission.double_o_agents
Spune-mi că nu e bine cumva! Nu este pentru uzul cotidian, dar bănuiește să cred. Deci aici Misiune
pot "cere" numai agenți care au licența de a ucide.
Un cuvânt despre sintaxa, deoarece ne-am îndepărtat de convențiile de numire și am folosit ceva mai expresiv double_o_agents
. Trebuie să menționăm numele clasei pentru a nu confunda Rails, care altfel ar putea aștepta să caute o clasă DoubleOAgent
. Puteți, bineînțeles, să aveți și amândouă Agent
asociațiile în vigoare - cele obișnuite și cele personalizate - iar Rails nu se va plânge.
clasa Misiune < ActiveRecord::Base has_many :agents has_many :double_o__agents, -> where (license_to_kill: true), nume_clasă: "Agent" end # => Mission.agents # => Mission.double_o_agents
Când interogați baza de date pentru înregistrări și nu aveți nevoie de toate datele, puteți alege să specificați exact ce doriți să vi se returneze. De ce? Deoarece datele returnate la Active Record vor fi în cele din urmă construite în obiecte Ruby noi. Să ne uităm la o strategie simplă pentru a evita inflația de memorie în aplicația Rails:
clasa Misiune < ActiveRecord::Base has_many :agents end class Agent < ActiveRecord::Base belongs_to :mission end
Agent.all.joins (misiune):
SELECT "agenți". * FROM "agenți" INNER JOIN "misiuni" ON "misiuni" "id" = "
Deci, această interogare returnează o listă de agenți cu o misiune din baza de date către Active Record - care apoi, la rândul ei, se dorește să construiască obiecte Ruby din ea. misiune
datele sunt disponibile deoarece datele din aceste rânduri se alătură rândurilor datelor agentului. Aceasta înseamnă că datele integrate sunt disponibile în timpul interogării, dar nu vor fi returnate la Active Record. Deci, veți avea aceste date pentru a efectua calcule, de exemplu.
Este deosebit de interesant, deoarece puteți utiliza datele care nu mai sunt trimise înapoi în aplicația dvs. Mai puține atribute care trebuie să fie construite în obiecte Ruby - care preiau memoria - pot fi o mare victorie. În general, gândiți-vă să trimiteți numai rândurile și coloanele absolut necesare înapoi de care aveți nevoie. În acest fel, puteți evita o bătaie destul de puțin.
Agent.all.joins (: mission) .where (misiuni: objective: "Salvarea lumii")
Doar o scurtă privire la sintaxa de aici: pentru că nu interogăm Agent
tabel prin Unde
, dar cei adunați :misiune
tabel, trebuie să precizăm că căutăm anumite misiuni
în a noastră UNDE
clauză.
SELECT "agenți". * FROM "agenți" INNER JOIN "misiuni" "ON" misiuni "" id "=" [["obiectiv", "salvarea lumii"]]
Utilizarea include
aici s-ar întoarce, de asemenea, misiuni în Active Record pentru încărcare dornică și să preia obiecte de Ruby de memorie.
A îmbina
vine la îndemână, de exemplu, atunci când dorim să combinăm o interogare cu agenții și misiunile asociate care au un anumit domeniu de aplicare definit de dvs. Putem lua două ActiveRecord :: Relația
obiecte și îmbinarea condițiilor lor. Sigur, nici un biggie, dar îmbina
este util dacă doriți să utilizați un anumit domeniu în timp ce utilizați o asociere.
Cu alte cuvinte, cu ce putem face îmbina
este filtrat de un domeniu denumit pe modelul îmbinat. Într-unul din exemplele anterioare, am folosit metode de clasă pentru a defini noi astfel de domenii.
clasa Misiune < ActiveRecord::Base has_many :agents def self.dangerous where(enemy: "Ernst Stavro Blofeld") end end class Agent < ActiveRecord::Base belongs_to :mission end
Agent.joins (: misiune) .merge (Mission.dangerous)
SELECT "agenții". * FROM "agenți" INNER JOIN "misiuni" "ON" misiuni "" id "=" [["inamic", "Ernst Stavro Blofeld"]]
Când încapsulem ce a periculos
misiunea este în cadrul Misiune
model, îl putem pune pe un a adera
prin intermediul îmbina
pe aici. Așadar, mișcarea logicii unor astfel de condiții la modelul relevant în care aparține este, pe de o parte, o tehnică frumoasă pentru a realiza cuplarea mai relaxată - nu vrem ca modelele noastre Active Record să cunoască o mulțime de detalii despre unul pe altul - și pe de altă parte de mână, vă oferă un API frumos în aderarea dvs. fără a sufla în fața dumneavoastră. Exemplul de mai jos fără fuzionare nu ar funcționa fără o eroare:
Agent.all.merge (Mission.dangerous)
SELECȚI "agenți". * FROM "agenți" WHERE "misiuni". "Inamic" =? [["inamic", "Ernst Stavro Blofeld"]]
Atunci când fuzionăm acum ActiveRecord :: Relația
obiecte pentru misiunile noastre pe agenții noștri, baza de date nu știe despre care misiuni vorbim. Trebuie să clarificăm care este asocierea de care avem nevoie și să aderăm mai întâi la datele misiunii - sau SQL devine confuz. Un ultim cireș deasupra. Putem încapsula acest lucru și mai bine prin implicarea agenților, de asemenea:
clasa Misiune < ActiveRecord::Base has_many :agents def self.dangerous where(enemy: "Ernst Stavro Blofeld") end end class Agent < ActiveRecord::Base belongs_to :mission def self.double_o_engagements joins(:mission).merge(Mission.dangerous) end end
Agent.double_o_engagements
SELECT "agenții". * FROM "agenți" INNER JOIN "misiuni" "ON" misiuni "" id "=" [["inamic", "Ernst Stavro Blofeld"]]
Asta eo cireșă dulce în cartea mea. Încapsulare, OOP propriu-zisă și o mare lizibilitate. potul cel mare!
Mai sus am văzut aparține lui
asociere în acțiune foarte mult. Să aruncăm o privire la aceasta dintr-o altă perspectivă și să aducem secțiuni de servicii secrete în mix:
clasă < ActiveRecord::Base has_many :agents end class Mission < ActiveRecord::Base has_many :agents end class Agent < ActiveRecord::Base belongs_to :mission belongs_to :section end
Deci, în acest scenariu, agenții ar avea nu numai un mission_id
dar și a SECTION_ID
. Până acum, bine. Să găsim toate secțiunile cu agenți cu o anumită misiune - deci secțiuni care au un fel de misiune.
Section.joins (: agenți)
SELECT "sectiuni" * FROM "sectiuni" agenti INNER JOIN "agenti" ON "" sectiuni "section_id" = "
Ai observat ceva? Un mic detaliu este diferit. Cheile străine sunt rotite. Aici solicităm o listă de secțiuni, dar folosim chei străine, cum ar fi: "agenți". "secțiunea_id" = "secțiuni" id "
. Cu alte cuvinte, căutăm o cheie străină de la o masă pe care o aderăm.
Agent.joins (misiune):
SELECT "agenți". * FROM "agenți" INNER JOIN "misiuni" ON "misiuni" "id" = "
Anterior ne-am alăturat printr-un aparține lui
Asociația arăta astfel: cheile străine au fost reflectate ("misiuni" "id" = "agenți". "mission_id"
) și au căutat cheia străină de la masa pe care o pornim.
Revenind la dvs. are multe
scenariu, acum vom obține o listă de secțiuni care se repetă deoarece au mai mulți agenți în fiecare secțiune, desigur. Deci, pentru fiecare coloană de agenți care se îmbină, obținem un rând pentru această secțiune sau secțiune_id-pe scurt, redăm în mod substanțial rânduri. Pentru a face acest lucru și mai amețitor, să aducem misiuni și în mix.
Section.joins (agenți:: misiune)
SELECT "sectiuni" * FROM "sectiuni" INNER JOIN "agenti" ON "agenti" "section_id" = "sectiuni". "Id" INNER JOIN "misiuni" ON " mission_id“
Verifică cele două INNER JOIN
părți. Încă cu mine? Noi "ajungem" prin intermediul agenților la misiunile lor din secțiunea agentului. Da, chestii pentru dureri de cap, știu. Ceea ce obținem sunt misiuni care se asociază indirect cu o anumită secțiune.
Ca rezultat, primim noi coloane, dar numărul de rânduri este același, care este returnat de această interogare. Ceea ce este trimis înapoi la Active Record - rezultând în construirea de noi obiecte Ruby - este, de asemenea, în continuare lista secțiunilor. Deci, atunci când avem mai multe misiuni care se întâmplă cu mai mulți agenți, vom primi din nou secțiuni duplicate pentru secțiunea noastră. Să mai filtrăm acest lucru:
(Misiuni: inamic: "Ernst Stavro Blofeld")
SELECT "sectiuni" * FROM "sectiuni" INNER JOIN "agenti" ON "agenti" "section_id" = "sectiuni". "Id" INNER JOIN "misiuni" ON " mission_id "WHERE" misiuni "." inamic "=" Ernst Stavro Blofeld "
Acum primim numai secțiuni returnate care sunt implicate în misiuni în care Ernst Stavro Blofeld este inamicul implicat. Cosmopolitan, deoarece unii răufăcători superiori s-ar putea gândi la ei înșiși, ar putea opera în mai multe secțiuni - să zicem secțiunile A și C, Statele Unite și, respectiv, Canada.
Dacă avem mai mulți agenți într-o anumită secțiune care lucrează la aceeași misiune pentru a opri Blofeld sau orice altceva, vom reveni din nou la noi în Active Record. Hai să fim puțin mai distinși în privința asta:
(Misiuni: inamic: "Ernst Stavro Blofeld".
SELECT DISTINCT "secțiunile" * FROM "secțiunile" INNER JOIN "agenți" ON "agenți". "Id" = "agenți". "mission_id" WHERE "misiuni". "inamic" = "Ernst Stavro Blofeld"
Ceea ce ne oferă acest lucru este numărul de secțiuni pe care Blofeld o exploatează - care sunt cunoscute - care au agenți activi în misiuni cu el ca dușman. Ca ultim pas, să mai facem din nou refactorizarea. Extragem acest lucru într-o metodă de clasă "mică" clasă
:
clasă < ActiveRecord::Base has_many :agents def self.critical joins(agents: :mission).where(missions: enemy: "Ernst Stavro Blofeld" ).distinct end end class Mission < ActiveRecord::Base has_many :agents end class Agent < ActiveRecord::Base belongs_to :mission belongs_to :section end
Puteți să refaceți acest aspect și să vă împărțiți responsabilitățile pentru a realiza cuplarea mai relaxată, dar să mergem mai departe.
De cele mai multe ori puteți să vă bazați pe înregistrarea activă scriind SQL pe care doriți pentru dvs. Asta inseamna ca stati in teren Ruby si nu trebuie sa va ingrijorati prea mult de detaliile bazei de date. Dar, uneori, trebuie să dați o gaură în teren SQL și să faceți propriul dvs. lucru. De exemplu, dacă trebuie să utilizați a STÂNGA
să se alăture și să iasă din comportamentul obișnuit al lui Active Record de a face o INTERIOR
se alăture implicit. se alătură
este o fereastră mică pentru a vă scrie propriul SQL personalizat dacă este necesar. Deschideți-l, introduceți codul de interogare personalizat, închideți fereastra și puteți continua să adăugați metode de interogare Active Record.
Să demonstram acest lucru cu un exemplu care implică gadget-uri. Să spunem de obicei un agent tipic are multe
gadget-uri, și vrem să găsim agenți care nu sunt echipați cu niciun fel de gadgeturi fantezie care să le ajute pe câmp. O adunare obișnuită nu ar aduce rezultate bune, de vreme ce ne interesează zero
-sau nul
în valorile de vorbire SQL ale acestor jucării de spionaj.
clasa Misiune < ActiveRecord::Base has_many :agents end class Agent < ActiveRecord::Base belongs_to :mission has_many :gadgets end class Gadget < ActiveRecord::Base belongs_to :agent end
Când facem a se alătură
operațiune, vom primi numai agenți returnați care sunt deja echipați cu gadgeturi deoarece agent_id
pe aceste gadget-uri nu este zero. Acesta este comportamentul așteptat al unei intrări interne implicite. Îmbinarea interioară se bazează pe un meci de pe ambele părți și returnează numai rânduri de date care se potrivesc cu această condiție. Un obiect gadget inexistent cu un zero
valoarea pentru un agent care nu poartă nici un obiect gadget nu se potrivește cu acest criteriu.
Agent.joins (: gadget-uri)
SELECT "agenți". * FROM "agenți" INNER JOIN "gadget-uri" ON "gadgets" "agent_id" = "
Suntem, pe de altă parte, în căutarea unor agenți schmuck care au nevoie de puțină dragoste de la conducătorul de carieră. Prima dvs. estimare ar fi putut să arate după cum urmează:
Agent.joins (: gadget-uri) .de unde (gadget-uri: agent_id: nil)
SELECT "agenții". * FROM "agenți" INNER JOIN "gadget-uri" ON "gadget-uri" "agent_id" = "agenți" "id" WHERE "gadgets"
Nu este rău, dar după cum puteți vedea din ieșirea SQL, nu se joacă de-a lungul și încă insistă asupra implicit INNER JOIN
. Acesta este un scenariu în care avem nevoie de unul EXTERIOR
alăturați-vă, deoarece o parte a "ecuației" noastre lipsește, ca să spunem așa. Căutăm rezultate pentru gadget-uri care nu există - mai precis pentru agenți fără gadgeturi.
Până acum, când am trecut un simbol la Active Record într-un concurs, se aștepta la o asociere. Cu un șir transmis, în schimb, se așteaptă ca acesta să fie un fragment real al codului SQL - o parte a interogării dvs..
Agent.joins ("gadgets la ieșire din stânga pe gadgets.agent_id = agents.id") unde (gadgets: agent_id: nil)
SELECT "agenți". * FROM "agenți" Lăsați gadget-urile OUT OUTER JOIN ON gadgets.agent_id = agents.id Unde "gadgets". "Agent_id" IS NULL
Sau, dacă sunteți curioși de agenții leneși fără misiuni - agățat, eventual, în Barbados sau oriunde s-ar potrivi personajul nostru ar arăta astfel:
Agent.joins ("misiuni LEFT OUTER JOIN ON missions.id = agents.mission_id") unde (misiuni: id: nil)
SELECT "agenți". * FROM "agenți" LEFT OUTOUN JOIN misiuni ON missions.id = agents.mission_id WHERE "misiuni". "Id" IS NULL
Asamblarea exterioară este versiunea mai cuprinzătoare de conectare, deoarece se va potrivi cu toate înregistrările din tabelele integrate, chiar dacă unele dintre aceste relații nu există încă. Deoarece această abordare nu este la fel de exclusivă ca și cea interioară, veți obține o grămadă de nils aici și acolo. Acest lucru poate fi informativ în unele cazuri, desigur, dar legăturile interioare sunt, totuși, de obicei ceea ce căutăm. Rails 5 ne va permite să folosim o metodă specializată numită left_outer_joins
în loc de astfel de cazuri. In cele din urma!
Un lucru mic pentru drum: păstrați aceste găuri de peeping în SQL teren cât mai mic posibil, dacă puteți. Veți face pe toată lumea - inclusiv pe viitorul vostru - o favoare extraordinară.
Obținerea înregistrării active pentru a scrie SQL eficient pentru tine este una din competențele principale pe care ar trebui să le îndepărtați de la această mini-serie pentru începători. În acest fel, veți obține și codul compatibil cu baza de date pe care o suportă - ceea ce înseamnă că interogările vor fi stabile în cadrul bazelor de date. Este necesar să înțelegeți nu numai cum să jucați cu Active Record, ci și SQL, care are o importanță egală.
Da, SQL poate fi plictisitor, plictisitor de citit si nu arata elegant, dar nu uita ca Rails inregistreaza Active Record in jurul SQL-ului si nu trebuie sa neglijezi intelegerea acestei tehnologii vitale - doar pentru ca Rails face foarte usor sa nu ai grija cel mai mult din timp. Eficiența este esențială pentru interogările bazei de date, mai ales dacă construiți ceva pentru publicul larg cu trafic intens.
Acum ieșiți pe internet și găsiți mai multe materiale pe SQL pentru al scoate din sistem - odată pentru totdeauna!