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

dzajic
fuente
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 []>
steveh7
fuente
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".

steveh7
fuente
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 .

Nathan Long
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.

Brandon
fuente
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')
bbrinck
fuente
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) }
Alex
fuente
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))}
Pan Thomakos
fuente
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
Agregué
1

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

where('false')
where('null')
fmnoise
fuente
2
Por cierto, tenga en cuenta que estos deben ser cadenas. where(false)o where(nil)simplemente se ignora.
mahemoff