In dit artikel leert u de basisprincipes van Active Record-query's en leert u onderweg enkele basisbeginselen over SQL. Het is bedoeld voor beginners die meer willen leren over databasequery's in Ruby on Rails.
Active Record wordt gebruikt voor het opvragen van de database. Het kan worden gebruikt met SQL, PostgresSQL en SQLite. Voor het ophalen van records uit uw database beschikt u over verschillende zoekmethoden. Het leuke eraan is dat je jezelf de moeite van het schrijven van onbewerkte SQL kunt besparen.
Wat doet een vindermethode echt? In principe drie dingen: uw aangeboden opties worden geconverteerd naar een SQL-query. Vervolgens wordt de SQL-query uitgevoerd en worden gegevens uit de database opgehaald. Voor elke rij in die resultatenlijst krijgen we ook nieuwe instantine Ruby-objecten van het model die overeenkomen met de query.
Als je nog niet eerder met SQL hebt gespeeld, zal ik mijn best doen om de dingen eenvoudig te houden en je kennis te laten maken met de basis. Volg de SQL-voorbeelden en probeer betekenis te geven aan deze eenvoudige query's. SQL is echt geen echte raketwetenschap, de syntaxis is even wennen. Dit zal hopelijk je eetlust opwekken om een aantal nuttige tutorials op te sporen die de lege plekken vullen.
Laten we een paar methoden bekijken die tot uw beschikking staan:
vind
eerste
laatste
find_by
allemaal
find_each
find_in_batches
waar
bestellen
begrenzing
compenseren
groep
met
Al deze keren een exemplaar terug van ActiveRecord :: Relation
. Een wat? Het is een klasse die is namespaceed binnen de module ActiveRecord
, en het stelt ons in staat om meerdere zoekmethoden aan te roepen en te ketenen. Dit object is het hart van de query-syntaxis die wordt gebruikt in Rails. Laten we de klasse van een dergelijk object bekijken en zelf zien:
Agent.where (naam: 'James Bond'). Class # => ActiveRecord :: Relation
vind
Met deze methode kunt u de primaire id van een object invoeren en dat ene object voor u ophalen. Als u een array met id's opgeeft, kunt u ook meerdere objecten ophalen.
bond = Agent.find (7)
SELECTEER "agents". * FROM "agents" WAAR "agents". "Id" =? LIMIET 1 [["id", 7]]
In deze SQL-regel staat dat u alles wilt selecteren (*
) attributen van de agenten
tabel en "filter" alleen de record met de id 7. Een limiet zorgt ervoor dat het slechts één record uit de database retourneert.
eerste
, laatste
Niet verwonderlijk, deze geven u de eerste en laatste records die kunnen worden geïdentificeerd aan de hand van hun primaire sleutel. Het interessante deel is echter dat u een optioneel nummer kunt opgeven dat u het eerste of laatste van dat aantal records oplevert.
enemy_agents = SpectreAgent.first (10) enemy_agents = SpectreAgent.last (10)
Onder de motorkap biedt u een nieuwe limiet voor het nummer dat u opgeeft en het oplopend of aflopend te bestellen.
SELECT "spectreagents". * FROM "spectreagents" ORDER BY "spectreagents". "Id" ASC LIMIT 10 SELECT "spectreagents". * FROM "spectreagents" ORDER BY "spectreagents". "Id" DESC LIMIT 10
find_by
Deze vinder retourneert het eerste object dat overeenkomt met de voorwaarde die u opgeeft.
bond = Agent.find_by (achternaam: 'Bond')
SELECTEER "agents". * FROM "agents" WAAR "agents". "Last_name" =? LIMIT 1 [["achternaam", "Obligatie"]]
Het is duidelijk dat we vaak een verzameling objecten met een bepaalde agenda moeten herhalen. Het ophalen van een enkel object of een paar geselecteerde met de hand is leuk, maar vaker wel dan niet, we willen dat Active Record objecten in batches ophaalt.
Gebruikers van allerlei soorten lijsten tonen is het brood en boter voor de meeste Rails-apps. Wat we nodig hebben, is een krachtige tool met een handige API om deze objecten voor ons te verzamelen - hopelijk op een manier die ons voorkomt om de betrokken SQL zelf meestal te schrijven.
allemaal
mi6_agents = Agents.all
SELECTEER "agenten". * VAN "agenten"
Deze methode is handig voor relatief kleine verzamelingen objecten. Probeer je voor te stellen dat je dit doet op een verzameling van alle Twitter-gebruikers. Nee, geen goed idee. Wat we willen is een meer verfijnde aanpak voor grotere tafelformaten.
Het ophalen van de hele tabel gaat niet op schaal! Waarom? Omdat we niet alleen om een heleboel objecten zouden vragen, zouden we ook één object per rij in deze tabel moeten bouwen en ze in een array in het geheugen moeten plaatsen. Ik hoop dat dit niet als een goed idee klinkt! Dus wat is de oplossing hiervoor? Batches! We verdelen deze collecties in batches die gemakkelijker te verwerken zijn. Woohoo!
Laten we eens kijken find_each
en find_in_batches
. Beide zijn vergelijkbaar maar gedragen zich anders in hoe ze objecten in blokken opbrengen. Ze accepteren een optie om de batchgrootte te regelen. De standaardinstelling is 1.000.
find_each
NewRecruit.find_each do | recruit | recruit.start_hellweek end
SELECTEER "newrecruits". * FROM "newrecruits" BESTELLING BY "newrecruits". "Id" ASC LIMIT 1000
In dit geval halen we een standaardbatch van 1.000 nieuwe rekruten op, brengen ze naar het blok en sturen ze week voor week naar de hel. Omdat batches collecties opdelen, kunnen we ze ook vertellen waar ze moeten beginnen begin
. Laten we zeggen dat we 3.000 mogelijke rekruten in één keer willen verwerken en willen starten bij 4.000.
NewRecruit.find_each (start: 4000, batch_size: 3000) do | recruit | recruit.start_hellweek end
SELECTEER "newrecruits". * FROM "newrecruits" WHERE ("newrecruits". "Id"> = 4000) BESTELLING OP "newrecruits". "Id" ASC LIMIT 3000
Om te herhalen, halen we eerst een batch van 3.000 Ruby-objecten op en sturen ze vervolgens naar het blok. begin
laat ons de ID van de records specificeren waar we willen beginnen met het ophalen van deze batch.
find_in_batches
Deze geeft zijn batch als een array aan het blok door - deze geeft het door aan een ander object dat de voorkeur geeft aan het verwerken van verzamelingen. De SQL is hier hetzelfde.
NewRecruit.find_in_batches (start: 2700, batch_size: 1350) do | rekruten | field_kitchen.prepare_food (rekruten) einde
waar
We moeten overgaan waar
voordat we verder gaan. Hiermee kunnen we voorwaarden opgeven die het aantal records beperken dat door onze query's wordt geretourneerd, een filter voor "where" om records uit de database op te halen. Als je met SQL hebt gespeeld WAAR
clausules dan kun je je gewoon meteen thuis voelen - hetzelfde met deze Ruby wikkel.
In SQL stelt dit ons in staat te specificeren welke tabelrij we willen beïnvloeden, in principe waar het aan een of ander criterium voldoet. Dit is trouwens een optionele clausule. In de onbewerkte SQL hieronder selecteren we alleen rekruten die wees zijn via WAAR
.
Selecteer een specifieke rij uit een tabel.
SELECTEER * VAN Recruits WHERE FamilyStatus = 'Orphan';
Via waar
, u kunt voorwaarden opgeven met tekenreeksen, hashes of arrays. Door dit alles samen te voegen, kunt u met Active Record op dergelijke omstandigheden filteren:
promising_candidates = Recruit.where ("family_status = 'orphan'")
SELECTEER "rekruten". * VAN "rekruten" WAAR (family_status = 'wees')
Best leuk, toch? Ik wil vermelden dat dit nog steeds een zoekbewerking is: we specificeren alleen hoe we deze lijst meteen willen filteren. Van de lijst met alle rekruten zal dit een gefilterde lijst van verweesde kandidaten retourneren. Dit voorbeeld is een stringvoorwaarde. Blijf uit de buurt van pure string-voorwaarden, omdat ze niet als veilig worden beschouwd vanwege hun kwetsbaarheid voor SQL-injecties.
In het bovenstaande voorbeeld plaatsen we de wees-
verander in de string met de voorwaarden. Dit wordt als een slechte praktijk beschouwd omdat het onveilig is. We moeten aan de variabele ontsnappen om deze beveiligingskwetsbaarheid te voorkomen. U moet de SQL-injectie lezen als dit totaal nieuws voor u is - uw database kan ervan afhankelijk zijn.
promising_candidates = Recruit.where ("family_status =?", 'orphan' ")
De ?
wordt als voorwaarde vervangen door de volgende waarde in de lijst met argumenten. Dus het vraagteken is eigenlijk een placeholder. U kunt ook meerdere voorwaarden opgeven met meerdere ?
en keten ze samen. In een real-life scenario gebruiken we een hash van params als deze:
promising_candidates = Recruit.where ("family_status =?", params [: rekruten])
Als u een groot aantal variabele voorwaarden hebt, moet u de tijdelijke aanduidingen voor sleutel / waarde gebruiken.
promising_candidates = Recruit.where ("family_status =: preferred_status EN iq> =: required_iq AND charming =: lady_killer", preferred_status: 'orphan', required_iq: 140, lady_killer: true)
SELECTEER "rekruten". * VAN "rekruten" WAAR (family_status = 'wees' EN iq> = 140 EN lady_killer = true)
Het bovenstaande voorbeeld is natuurlijk gek, maar het toont duidelijk de voordelen van de plaatshoudernotatie. De hashnotatie is over het algemeen beslist leesbaarder.
promising_candidates = Recruit.where (family_status: 'orphan') promising_candidates = Recruit.where ('charmant': waar)
Zoals je kunt zien, kun je met symbolen of touwtjes naar je toe gaan. Laten we dit gedeelte met bereiken en negatieve condities sluiten via NOT.
promising_candidates = Recruit.where (verjaardag: ('1994-01-01' ... '2000-01-01'))
Twee punten en je kunt elk bereik instellen dat je nodig hebt.
promising_candidates = Recruit.where.not (karakter: 'lafaard')
Je kunt de niet
op de waar
om alle lafaards eruit te filteren en alleen resultaten te krijgen die niet dat specifieke, ongewenste kenmerk hebben. Onder de motorkap, een !=
ontkent het WHERE "filter".
SELECTEER "rekruten". * VAN "rekruten" WAAR ("rekruten". "Teken"! =?) [["Karakter", "lafaard"]]
bestellen
Om je er niet dood mee te vervelen, laten we dit snel doen.
kandidaten = Recruit.order (: date_of_birth)
kandidaten = Recruit.order (: date_of_birth,: desc)
Van toepassing zijn : ASC
of : desc
om het dienovereenkomstig te sorteren. Dat is het eigenlijk, dus laten we doorgaan!
begrenzing
U kunt het aantal geretourneerde records reduceren tot een bepaald aantal. Zoals eerder vermeld, zult u meestal niet alle geretourneerde gegevens nodig hebben. Het onderstaande voorbeeld geeft u de eerste vijf rekruten in de database - de eerste vijf id's.
five_candidates = Recruit.limit (5)
SELECTEER "rekruten". * VAN "rekruten" LIMIET 5
compenseren
Als je je ooit hebt afgevraagd hoe paginering werkt onder de motorkap, begrenzing
en compenseren
-in combinatie - doe het harde werk. begrenzing
kan op zichzelf staan, maar compenseren
hangt af van de eerste.
Het instellen van een offset is vooral handig voor paginering en laat u het gewenste aantal rijen in de database overslaan. Pagina twee van een kandidatenlijst kan als volgt worden opgezocht:
Recruit.limit (20) .offset (20)
De SQL ziet er als volgt uit:
SELECTEER "rekruten". * VAN "rekruten" LIMIET 20 OFFSET 20
Nogmaals, we selecteren alle kolommen van de Rekruut
database model, beperkend de records geretourneerd naar 20 Ruby-objecten van Class Recruit en spring over de eerste 20.
Laten we zeggen dat we een lijst van rekruten willen die gegroepeerd zijn op hun IQ's. In SQL zou dit er ongeveer zo uit kunnen zien.
SELECTEER "rekruten". * VAN "rekruten" GROEP VAN "rekruten". "Iq"
Zo krijg je een lijst waarin je ziet welke mogelijke rekruten een IQ hebben van laten we zeggen 120, en dan een andere groep van 140, enzovoort, wat hun IQ's ook zijn en hoeveel er onder een specifiek nummer vallen. Dus wanneer twee rekruten hetzelfde IQ van 130 hebben, zouden ze bij elkaar worden gegroepeerd.
Een andere lijst zou gegroepeerd kunnen zijn door mogelijke kandidaten die last hebben van claustrofobie, hoogtevrees of die medisch niet geschikt zijn om te duiken. De Active Record-query zou er eenvoudig als volgt uitzien:
groep
Candidate.group (: iq)
Als we het aantal kandidaten tellen, krijgen we een zeer nuttige hash terug.
Candidate.group (: iq) .count # => 130 => 7, 134 => 4, 135 => 3, 138 => 2, 140 => 1, 141 => 1
Daar gaan we - we hebben zeven mogelijke rekruten met een IQ van 130 en slechts één met 141. De resulterende SQL ziet er als volgt uit:
SELECTEER COUNT (*) AS count_all, iq AS iq VAN "kandidaten" GROUP BY "kandidaten". "Iq"
Het belangrijke stuk is het GROEP DOOR
een deel. Zoals u kunt zien, gebruiken we de kandidaten-tabel om hun id's te krijgen. Wat u ook uit dit eenvoudige voorbeeld kunt opmerken, is hoeveel handiger de Active Record-versies lezen en schrijven. Stel je voor dat je dit met de hand doet aan meer extravagante voorbeelden. Natuurlijk, soms moet dat, maar de hele tijd is duidelijk een pijn die we graag vermijden.
met
We kunnen deze groep nog meer specificeren door te gebruiken HEBBEN
-een soort filter voor de groep
. Op die manier, met
is een soort van WAAR
clausule voor GROEP
. Met andere woorden, met
is afhankelijk van het gebruik groep
.
Recruit.having ('iq>?', 134) .group (: iq)
SELECTEER "rekruten". * VAN "rekruten" GROEP DOOR "rekruten". "Iq" MET iq> '134'
We hebben nu onze kandidaten gegroepeerd in lijsten met mensen met een minimum-IQ van 135. Laten we ze tellen om een aantal statistieken te krijgen:
Recruit.having ('iq>?', 134) .group (: iq) .count # => 135 => 3, 138 => 2, 140 => 1, 141 => 1
SELECTEER COUNT (*) AS count_all, iq AS iq FROM "recruits" GROUP BY "rekruten". "Iq" HAVING iq> '134'
We kunnen deze ook mixen en matchen en zien bijvoorbeeld welke kandidaten met IQ's hoger dan 140 vastzitten in relaties of niet.
Recruit.having ('iq>?', 140) .group (: family_status)
SELECTEER "rekruten". * VAN "rekruten" GROEP DOOR "rekruten". "Family_status" MET iq> '140'
Het tellen van deze groepen is nu allemaal te gemakkelijk:
Recruit.having ('iq>?', 140) .group (: family_status) .count # => "married" => 2, "single" => 1
SELECTEER COUNT (*) AS count_all, family_status AS family_status FROM "recruits" GROUP BY "rekruten". "Family_status" HAVING iq> '140'
Ik hoop dat dit een nuttige eerste blik was op wat Active Record te bieden heeft om uw zoekopdracht zo leesbaar en gemakkelijk mogelijk te maken. Over het algemeen zou ik zeggen dat het een uitstekende wrapper is die je ervan weerhoudt om meestal met de hand SQL te schrijven.
In het volgende artikel zullen we een paar meer betrokken vinders bekijken en verder gaan met wat we tot nu toe hebben geleerd.