Wie kann eine leere ActiveRecord-Beziehung zurückgegeben werden?

246

Wenn ich einen Gültigkeitsbereich mit einem Lambda habe und abhängig vom Wert des Arguments ein Argument benötigt, weiß ich möglicherweise, dass es keine Übereinstimmungen gibt, möchte aber dennoch eine Beziehung zurückgeben, kein leeres Array:

scope :for_users, lambda { |users| users.any? ? where("user_id IN (?)", users.map(&:id).join(',')) : [] }

Was ich wirklich will, ist eine "none" -Methode, das Gegenteil von "all", die eine Beziehung zurückgibt, die noch verkettet werden kann, aber dazu führt, dass die Abfrage kurzgeschlossen wird.

dzajic
quelle
Wenn Sie die Abfrage einfach ausführen lassen, wird eine Beziehung zurückgegeben: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. Versuchen Sie, die Abfrage insgesamt zu vermeiden?
Brian Deterling
1
Richtig. Wenn ich weiß, dass es unmöglich Übereinstimmungen geben kann, könnte die Abfrage im Idealfall insgesamt vermieden werden. Ich habe dies einfach zu ActiveRecord :: Base hinzugefügt: "def self.none; where (: id => 0); end" Scheint für das, was ich brauche, gut zu funktionieren.
Dzajic
1
> Versuchen Sie, die Abfrage insgesamt zu vermeiden? wäre absolut sinnvoll, irgendwie lahm müssen wir dafür die DB treffen
dolzenko

Antworten:

478

In Rails 4 gibt es jetzt einen "richtigen" Mechanismus:

>> Model.none 
=> #<ActiveRecord::Relation []>
steveh7
quelle
14
Bisher wurde dies nicht auf 3.2 oder früher zurückportiert. Einziger Rand (4.0)
Chris Bloom
2
Ich habe es gerade mit Rails 4.0.5 versucht und es funktioniert. Diese Funktion hat es bis zur Version Rails 4.0 geschafft.
Entwickeln Sie
3
@ AugustinRiedinger Model.scopedmacht, was Sie in Schienen 3 suchen.
Tim Diggins
9
Ab Rails 4.0.5 Model.nonefunktioniert nicht. Sie müssen einen Ihrer echten Modellnamen verwenden, z. B. User.noneoder was-haben-Sie.
Grant Birchmeier
77

Eine portablere Lösung, die keine "ID" -Spalte benötigt und nicht davon ausgeht, dass es keine Zeile mit der ID 0 gibt:

scope :none, where("1 = 0")

Ich bin immer noch auf der Suche nach einem "richtigen" Weg.

steveh7
quelle
3
Ja, ich bin wirklich überrascht, dass diese Antworten die besten sind, die wir haben. Ich denke, ActiveRecord / Arel muss noch ziemlich unreif sein. Wenn ich durch die Reise gehen müsste, um ein leeres Array in Ruby zu erstellen, wäre ich wirklich verärgert. Im Grunde das Gleiche hier.
Purplejacket
Obwohl etwas hackish, dies ist der richtige Weg für Rails 3.2. Für Rails 4 siehe die andere Antwort von @ steveh7 hier: stackoverflow.com/a/10001043/307308
scarver2
scope :none, -> { where("false") }
Nroose
43

Kommen in Schienen 4

In Rails 4 wird eine Verkettung ActiveRecord::NullRelationvon Anrufen wie zurückgegeben Post.none.

Weder es noch verkettete Methoden erzeugen Abfragen an die Datenbank.

Nach den Kommentaren:

Die zurückgegebene ActiveRecord :: NullRelation erbt von Relation und implementiert das Null-Objektmuster. Es ist ein Objekt mit definiertem Nullverhalten und gibt immer ein leeres Array von Datensätzen zurück, ohne die Datenbank abzufragen.

Siehe den Quellcode .

Nathan Long
quelle
42

Sie können einen Bereich mit dem Namen "none" hinzufügen:

scope :none, where(:id => nil).where("id IS NOT ?", nil)

Dadurch erhalten Sie eine leere ActiveRecord :: Relation

Sie können es auch in einem Initialisierer zu ActiveRecord :: Base hinzufügen (wenn Sie möchten):

class ActiveRecord::Base
 def self.none
   where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil)))
 end
end

Viele Möglichkeiten, um so etwas zu bekommen, aber sicherlich nicht das Beste, um in einer Codebasis zu bleiben. Ich habe den Bereich verwendet: keine, wenn ich umgestalte und feststelle, dass ich für kurze Zeit eine leere ActiveRecord :: Relation garantieren muss.

Brandon
quelle
14
where('1=2')könnte etwas prägnanter sein
Marcin Raczkowski
10
Falls Sie nicht zur neuen "richtigen" Antwort scrollen, gehen Model.noneSie wie folgt vor.
Joe Essey
26
scope :none, limit(0)

Ist eine gefährliche Lösung, da Ihr Zielfernrohr möglicherweise verkettet ist.

User.none.first

gibt den ersten Benutzer zurück. Es ist sicherer zu bedienen

scope :none, where('1 = 0')
bbrinck
quelle
2
Dieser ist der richtige 'Bereich: keiner, wobei (' 1 = 0 ')'. der andere wird scheitern, wenn Sie Paginierung haben
Federico
14

Ich denke, ich bevorzuge die Art und Weise, wie dies aussieht, gegenüber den anderen Optionen:

scope :none, limit(0)

Zu so etwas führen:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }
Alex
quelle
Ich bevorzuge dieses hier. Ich bin mir nicht sicher, warum where(false)ich den Job nicht machen sollte - er lässt den Umfang unverändert.
Aceofspades
4
Beachten Sie, dass limit(0)dies überschrieben wird, wenn Sie .firstoder .lastspäter in der Kette anrufen , da Rails LIMIT 1an diese Abfrage angehängt wird .
Zykadelic
2
@aceofspades wo (false) nicht funktioniert (Rails 3.0), aber wo ('false') funktioniert. Nicht, dass es dich jetzt wahrscheinlich interessiert, es ist 2013 :)
Ritchie
Danke @Ritchie, seitdem haben wir meiner Meinung nach auch die noneunten erwähnte Beziehung.
Aceofspades
3

Verwenden Sie Scoped:

scope: for_users, lambda {| users | users.any? ? where ("user_id IN (?)", users.map (&: id) .join (',')): scoped}

Sie können Ihren Code aber auch vereinfachen mit:

scope: for_users, lambda {| users | wo (: user_id => users.map (&: id)) wenn users.any? }}

Wenn Sie ein leeres Ergebnis wünschen, verwenden Sie dies (entfernen Sie die if-Bedingung):

scope: for_users, lambda {| users | where (: user_id => users.map (&: id))}
Pan Thomakos
quelle
Wenn Sie "scoped" oder nil zurückgeben, wird nicht das erreicht, was ich möchte, dh die Ergebnisse werden auf Null begrenzt. Die Rückgabe von "scoped" oder nil hat keine Auswirkung auf den Gültigkeitsbereich (was in einigen Fällen nützlich ist, in meinem jedoch nicht). Ich habe meine eigene Antwort gefunden (siehe Kommentare oben).
Dzajic
Ich habe eine einfache Lösung für Sie hinzugefügt, um auch ein leeres Ergebnis zurückzugeben :)
Pan Thomakos
1

Es gibt auch Varianten, aber alle fordern db an

where('false')
where('null')
fmnoise
quelle
2
Übrigens: Beachten Sie, dass dies Zeichenfolgen sein müssen. where(false)oder where(nil)wird einfach ignoriert.
Mahemoff