Come restituire una relazione ActiveRecord vuota?


246

Se ho un ambito con un lambda e ci vuole un argomento, a seconda del valore dell'argomento, potrei sapere che non ci saranno corrispondenze, ma voglio comunque restituire una relazione, non un array vuoto:

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

Quello che voglio davvero è un metodo "none", l'opposto di "all", che restituisce una relazione che può ancora essere concatenata, ma si traduce in un cortocircuito della query.


Se si lascia la query, eseguirla restituirà una relazione: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. Stai cercando di evitare del tutto la query?
Brian Deterling,

1
Corretta. Se so che non ci possono essere corrispondenze, idealmente, la query potrebbe essere evitata del tutto. Ho semplicemente aggiunto questo ad ActiveRecord :: Base: "def self.none; where (: id => 0); end" Sembra funzionare bene per quello che mi serve.
dzajic,

1
> Stai cercando di evitare del tutto la query? avrebbe
perfettamente

Risposte:


478

Ora esiste un meccanismo "corretto" in Rails 4:

>> Model.none 
=> #<ActiveRecord::Relation []>

14
Finora questo non è stato riportato a 3.2 o precedenti. Only edge (4.0)
Chris Bloom,


2
Ho appena provato con Rails 4.0.5 e funziona. Questa funzione è arrivata alla versione Rails 4.0.
Evolve il

3
@AugustinRiedinger Model.scopedfa quello che stai cercando su rotaie 3.
Tim Diggins,

9
A partire da Rails 4.0.5, Model.nonenon funziona. È necessario utilizzare uno dei nomi dei modelli reali , ad esempio User.noneo what-have-you.
Concedi Birchmeier il

77

Una soluzione più portatile che non richiede una colonna "id" e non presuppone che non ci sarà una riga con un ID di 0:

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

Sto ancora cercando un modo più "corretto".


3
Sì, sono davvero sorpreso che queste risposte siano le migliori che abbiamo. Penso che ActiveRecord / Arel debba essere ancora abbastanza immaturo. Se dovessi passare perambulazioni per creare un array vuoto in Ruby, sarei davvero infastidito. Stessa cosa qui, praticamente.
Purplejacket,

Sebbene in qualche modo hacker, questo è il modo corretto per Rails 3.2. Per Rails 4, vedi altra risposta @ s steveh7' qui: stackoverflow.com/a/10001043/307308
scarver2

scope :none, -> { where("false") }
Annullato il

43

Disponibile in Rails 4

In Rails 4, un chainable ActiveRecord::NullRelationverrà restituito da chiamate simili Post.none.

Né esso, né i metodi concatenati, genereranno query nel database.

Secondo i commenti:

ActiveRecord :: NullRelation restituito eredita da Relation e implementa il modello Null Object. È un oggetto con comportamento nullo definito e restituisce sempre una matrice vuota di record senza interrogare il database.

Vedi il codice sorgente .


42

È possibile aggiungere un ambito chiamato "nessuno":

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

Questo ti darà un ActiveRecord :: Relation vuoto

Puoi anche aggiungerlo ad ActiveRecord :: Base in un inizializzatore (se vuoi):

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

Un sacco di modi per ottenere qualcosa del genere, ma sicuramente non è la cosa migliore da conservare in una base di codice. Ho usato l'ambito: nessuno durante il refactoring e trovando che devo garantire un ActiveRecord :: Relation vuoto per un breve periodo.


14
where('1=2')potrebbe essere un po 'più conciso
Marcin Raczkowski il

10
Nel caso in cui non scorri verso il basso fino alla nuova risposta "corretta": Model.noneè come lo faresti.
Joe Essey,

26
scope :none, limit(0)

È una soluzione pericolosa perché il tuo ambito potrebbe essere incatenato.

User.none.first

restituirà il primo utente. È più sicuro da usare

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

2
Questo è quello giusto 'scope: none, where (' 1 = 0 ')'. l'altro fallirà se hai impaginazione
Federico

14

Penso di preferire il modo in cui questo sembra alle altre opzioni:

scope :none, limit(0)

Portando a qualcosa del genere:

scope :users, lambda { |ids| ids.present? ? where("user_id IN (?)", ids) : limit(0) }

Preferisco questo. Non sono sicuro del motivo per cui where(false)non farebbe il lavoro: lascia l'ambito invariato.
aceofspades,

4
Fai attenzione che limit(0)verrà sovrascritto se chiami .firsto .lastsuccessivamente nella catena, poiché Rails aggiungerà LIMIT 1a quella query.
zykadelic,

2
@aceofspades dove (false) non funziona (Rails 3.0) ma dove ('false') funziona. Non che probabilmente ti importi ora che è il 2013 :)
Ritchie il

Grazie @Ritchie, da allora penso che abbiamo anche la nonerelazione come indicato di seguito.
aceofspades,

3

Usa ambito:

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

Ma puoi anche semplificare il tuo codice con:

ambito: for_users, lambda {| users | dove (: user_id => users.map (&: id)) se users.any? }

Se si desidera un risultato vuoto, utilizzare questo (rimuovere la condizione if):

ambito: for_users, lambda {| users | dove (: user_id => users.map (&: id))}

Restituire "scoped" o zero non ottiene ciò che voglio, che è di limitare i risultati a zero. Restituire "scoped" o zero non ha alcun effetto sull'ambito (che è utile in alcuni casi, ma non nel mio). Ho trovato la mia risposta (vedi commenti sopra).
dzajic,

Ho aggiunto anche una soluzione semplice per restituire un risultato vuoto :)
Pan Thomakos


1

Ci sono anche varianti, ma tutte stanno facendo richiesta di db

where('false')
where('null')

2
BTW Nota che devono essere stringhe. where(false)o where(nil)viene semplicemente ignorato.
mahemoff,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.