Wie verkettet Rails ActiveRecord "where" -Klauseln ohne mehrere Abfragen?

75

Ich bin ein PHP-Entwickler, der die Faszination von Ruby on Rails lernt. Ich liebe ActiveRecord und habe etwas wirklich Interessantes bemerkt. So erkennen ActiveRecord-Methoden das Ende der Methodenkette, um die Abfrage auszuführen.

@person = Person.where(name: 'Jason').where(age: 26)

# In my humble imagination I'd think that each where() executes a database query
# But in reality, it doesn't until the last method in the chain

Wie funktioniert diese Zauberei?

Ryan
quelle
Bei der letzten Methode ist dies nicht der Fall. Es gibt keine Möglichkeit, das zu wissen. Überlegen Sie x = Person.where(..); @person = x.where(..), was identisch funktionieren soll. Es macht es einige Zeit später, also was ist der Auslöser? ;-)

Antworten:

157

Die whereMethode gibt ein ActiveRecord::RelationObjekt zurück, und dieses Objekt gibt selbst keine Datenbankabfrage aus. Hier kommt es darauf an, wo Sie dieses Objekt verwenden.

In der Konsole machen Sie wahrscheinlich Folgendes:

@person = Person.where(name: "Jason")

Und dann gibt blammo eine Datenbankabfrage aus und gibt ein Array von allen mit dem Namen Jason zurück. Ja, aktiver Rekord!

Aber dann machst du so etwas:

@person = Person.where(name: "Jason").where(age: 26)

Und dann gibt das eine andere Abfrage aus, aber diese ist für Leute, die Jason heißen und 26 Jahre alt sind. Aber es gibt nur eine Abfrage, also wohin ging die andere Abfrage?


Wie andere vorgeschlagen haben, geschieht dies, weil die whereMethode ein Proxy-Objekt zurückgibt. Es führt keine Abfrage durch und gibt ein Dataset zurück, es sei denn, es wird dazu aufgefordert.

Wenn Sie etwas in der Konsole ausführen , wird die überprüfte Version des Ergebnisses dessen ausgegeben, was auch immer Sie ausgeführt haben. Wenn Sie setzen 1in der Konsole und drücken Sie die Eingabetaste, erhalten Sie 1zurück , weil 1.inspectist 1. Magie! Gleiches gilt für "1". Eine Vielzahl anderer Objekte nicht über eine inspectMethode definiert und so Ruby des auf zurückfällt Objectdas wieder etwas grässlich wie <Object#23adbf42560>.

Auf jedem einzelnen ActiveRecord::RelationObjekt ist die inspectMethode definiert, sodass eine Abfrage ausgelöst wird. Wenn Sie die Abfrage in Ihre Konsole schreiben, ruft IRB inspectden Rückgabewert dieser Abfrage auf und gibt etwas aus, das fast für Menschen lesbar ist, wie das Array, das Sie sehen würden.


Wenn Sie dies nur in einem Standard-Ruby-Skript ausgeben, wird keine Abfrage ausgeführt, bis das Objekt (via inspect) inspiziert oder durch using iteriert eachwurde oder die to_aMethode aufgerufen wurde.

Bis eines dieser drei Dinge passieren, können Sie Kette als viele whereErklärungen, wie Sie mögen und dann , wenn Sie tun Anruf inspect, to_aoder eachauf sie, dann wird es schließlich die Abfrage auszuführen.

Ryan Bigg
quelle
1
Relevanter Railscast , der dies mit dem Rails-Quellcode erklärt.
Flexus
8

Es gibt eine Reihe von Methoden, die als "Kicker" bezeichnet werden und die Abfrage an die Datenbank tatsächlich auslösen. Zuvor erstellen sie lediglich AST-Knoten, die nach dem Kick das eigentliche SQL (oder die Sprache, in die kompiliert wird) generieren und die Abfrage ausführen.

In diesem Blog-Beitrag finden Sie eine ausführlichere Erklärung dazu.

x1a4
quelle
4

Sie können den Code lesen, aber ein Konzept hier ist das Proxy-Muster.

Wahrscheinlich ist @person nicht das eigentliche Objekt, sondern ein Proxy für dieses Objekt. Wenn Sie dort ein Attribut benötigen, führt der aktive Datensatz die Abfrage schließlich aus. Der Ruhezustand hat das gleiche Konzept.

Tiago Peczenyj
quelle
-2

Vielleicht etwas zu spät, aber Sie können einen Hash verwenden:

@person = Person.where({name: "Jason", age: 26})

Resultierende Abfrage:

SELECT "person".* FROM "person"  WHERE "person"."name" = 'Jason' AND "person"."age" = 26
Robert Cvjetković
quelle
2
person = Person.where ({Name: "Jason", Alter: 26}) und person = Person.where (Name: "Jason", Alter: 26) sind genau gleich - Ruby verwandelt die Argumente für die Sekunde in einen Hash automatisch
Ghoti