¿Cómo devolver una relación ActiveRecord vacía?


246

Si tengo un alcance con una lambda y requiere un argumento, dependiendo del valor del argumento, podría saber que no habrá coincidencias, pero aún quiero devolver una relación, no una matriz vacía:

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

Lo que realmente quiero es un método "ninguno", lo opuesto a "todos", que devuelve una relación que aún puede encadenarse, pero da como resultado un cortocircuito en la consulta.


Si solo deja la consulta, ejecútela y devolverá una relación: User.where ('id in (?)', []). Class => ActiveRecord :: Relation. ¿Estás tratando de evitar la consulta por completo?
Brian Deterling

1
Correcto. Si sé que no puede haber coincidencias, idealmente, la consulta podría evitarse por completo. Simplemente agregué esto a ActiveRecord :: Base: "def self.none; where (: id => 0); end" Parece funcionar bien para lo que necesito.
dzajic

1
> ¿Estás tratando de evitar la consulta por completo? sería totalmente sentido, algo escaso que tenga que pulsar DB para que
dolzenko

Respuestas:


478

Ahora hay un mecanismo "correcto" en Rails 4:

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

14
Hasta ahora, esto no se ha vuelto a portar a 3.2 o anterior. Only edge (4.0)
Chris Bloom


2
Acabo de probar con Rails 4.0.5 y está funcionando. Esta característica llegó a la versión Rails 4.0.
Evoluciona el

3
@AugustinRiedinger Model.scopedhace lo que estás buscando en los rieles 3.
Tim Diggins

99
A partir de Rails 4.0.5, Model.noneno funciona. Debe usar uno de sus nombres de modelo reales , por ejemplo, User.noneo lo que tenga.
Grant Birchmeier

77

Una solución más portátil que no requiere una columna "id" y no supone que no habrá una fila con un id de 0:

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

Todavía estoy buscando una forma más "correcta".


3
Sí, estoy realmente sorprendido de que estas respuestas sean las mejores que tenemos. Creo que ActiveRecord / Arel todavía debe ser bastante inmaduro. Si tuviera que pasar por las deambulaciones para crear una matriz vacía en Ruby, me molestaría mucho. Lo mismo aquí, básicamente.
Purplejacket

Aunque algo hack, esta es la forma correcta para Rails 3.2. Para Rails 4, consulte la otra respuesta de @ steveh7 aquí: stackoverflow.com/a/10001043/307308
scarver2

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

43

Próximamente en Rails 4

En Rails 4, se devolverá un encadenable ActiveRecord::NullRelationde llamadas comoPost.none .

Ni él ni los métodos encadenados generarán consultas a la base de datos.

Según los comentarios:

ActiveRecord :: NullRelation devuelto hereda de Relation e implementa el patrón de objeto nulo. Es un objeto con un comportamiento nulo definido y siempre devuelve una matriz vacía de registros sin consultar la base de datos.

Ver el código fuente .


42

Puede agregar un ámbito llamado "ninguno":

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

Eso le dará un ActiveRecord vacío :: Relación

También puede agregarlo a ActiveRecord :: Base en un inicializador (si lo desea):

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

Hay muchas maneras de obtener algo como esto, pero ciertamente no es lo mejor para mantener en una base de código. He utilizado el alcance: ninguno al refactorizar y encontrar que necesito garantizar un ActiveRecord :: Relation vacío por un corto tiempo.


14
where('1=2')podría ser un poco más conciso
Marcin Raczkowski

10
En caso de que no se desplace hacia la nueva respuesta 'correcta': así Model.nonees como lo haría.
Joe Essey

26
scope :none, limit(0)

Es una solución peligrosa porque su alcance podría estar encadenado.

Usuario.ninguno.primero

devolverá el primer usuario. Es más seguro usar

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

2
Este es el correcto 'alcance: ninguno, donde (' 1 = 0 ')'. el otro fallará si tiene paginación
Federico

14

Creo que prefiero la forma en que esto se ve a las otras opciones:

scope :none, limit(0)

Llevando a algo como esto:

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

Prefiero éste. No estoy seguro de por where(false)qué no haría el trabajo: deja el alcance sin cambios.
aceofspades

44
Tenga en cuenta que limit(0)se anulará si llama .firsto .lastmás adelante en la cadena, ya que Rails se agregará LIMIT 1a esa consulta.
zykadelic

2
@aceofspades donde (false) no funciona (Rails 3.0) pero donde ('false') sí funciona. No es que probablemente te importe ahora es 2013 :)
Ritchie

Gracias @Ritchie, desde entonces creo que también tenemos la nonerelación que se menciona a continuación.
aceofspades

3

Uso con alcance:

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

Pero también puede simplificar su código con:

alcance: for_users, lambda {| usuarios | donde (: user_id => users.map (&: id)) if users.any? }

Si desea un resultado vacío, use esto (elimine la condición if):

alcance: for_users, lambda {| usuarios | donde (: user_id => users.map (&: id))}

Devolver "scoped" o nil no logra lo que quiero, que es limitar los resultados a cero. Devolver "alcance" o nulo no tiene ningún efecto en el alcance (que es útil en algunos casos, pero no en el mío). Se me ocurrió mi propia respuesta (ver comentarios más arriba).
dzajic



1

También hay variantes, pero todas están solicitando a db

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

2
Por cierto, tenga en cuenta que estos deben ser cadenas. where(false)o where(nil)simplemente se ignora.
mahemoff
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.