¿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.