Combina dos objetos ActiveRecord :: Relation

174

Supongamos que tengo los siguientes dos objetos:

first_name_relation = User.where(:first_name => 'Tobias') # ActiveRecord::Relation
last_name_relation  = User.where(:last_name  => 'Fünke') # ActiveRecord::Relation

¿Es posible combinar las dos relaciones para producir un ActiveRecord::Relationobjeto que contenga ambas condiciones?

Nota: Soy consciente de que puedo encadenar las rutas para obtener este comportamiento, lo que realmente me interesa es el caso en el que tengo dos ActiveRecord::Relationobjetos separados .

Patrick Klingemann
fuente
Puede ser de ayuda: ActiveRecord O consulta Notación de hash
potashin

Respuestas:

202

Si desea combinar usando AND(intersección), use merge:

first_name_relation.merge(last_name_relation)

Si desea combinar usando OR(union), use :or

first_name_relation.or(last_name_relation)

Solo en ActiveRecord 5+; para 4.2 instale where-or backport.

Andrew Marshall
fuente
¿Qué tal si quiero que el resultado sea un ActiveRecord::Relationtipo?
Nueva Alejandría
1
@NewAlexandria Sí, en todos los casos, Rails te está mintiendo sobre la clase del objeto.
Andrew Marshall
77
¿Existe una versión "O" de fusión?
Arcolye
8
@minohimself Probablemente porque ese es el resultado real de fusionar sus dos relaciones. Tenga en cuenta que mergees una intersección, no una unión.
Andrew Marshall
55
Para referencia futura, el #ormétodo se agregó a ActiveRecord :: Relation en enero de 2015, y formará parte de Rails 5.0 , que se enviará a fines de 2015. Permite el uso del operador OR para combinar cláusulas WHERE o HAVING. Puede pagar HEAD si lo necesita antes del lanzamiento oficial. Ver solicitud de combinación de extracción # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Claudio Floreani
37

Los objetos de relación se pueden convertir en matrices. Esto niega poder usar cualquier método ActiveRecord en ellos después, pero no necesité hacerlo. Hice esto:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, rieles 3.2

thatmiddleway
fuente
Actualización: lástima que esto no se fusione por fecha
Daniel Morris
68
No actúa como una matriz, convierte tu relación a una matriz. Entonces ya no puedes usar métodos ActiveRecord como .where () ...
Augustin Riedinger
1
Si no le importa que el tipo de retorno sea una relación, puede combinar varios resultados con first_name_relation | last_name_relation. El "|" El operador también trabaja en múltiples relaciones. El valor del resultado es una matriz.
MichaelZ
11
La pregunta original era: "¿es posible combinar las dos relaciones para producir un objeto ActiveRecord :: Relation que contenga ambas condiciones?" Esta respuesta devuelve una matriz ...
courtimas
Esta es una solución EXCELENTE para muchos casos. Si solo necesita una enumeración de registros para recorrer (por ejemplo, no le importa si se trata de una matriz o una relación ActiveRecord ::) y no le importa la sobrecarga de dos consultas, esto resuelve el problema con una sintaxis simple y obvia. ¡Ojalá pudiera +2!
David Hempy
21

mergeen realidad no funciona así OR. Es simplemente intersección ( AND)

Luché con este problema para combinar los objetos ActiveRecord :: Relation en uno y no encontré ninguna solución que funcionara para mí.

En lugar de buscar el método correcto para crear una unión a partir de estos dos conjuntos, me concentré en el álgebra de conjuntos. Puedes hacerlo de manera diferente usando la ley de De Morgan

ActiveRecord proporciona el método de fusión (AND) y también puede usar el método not o none_of (NOT).

search.where.none_of(search.where.not(id: ids_to_exclude).merge(search.where.not("title ILIKE ?", "%#{query}%")))

Tienes aquí (A u B) '= A' ^ B '

ACTUALIZACIÓN: La solución anterior es buena para casos más complejos. En su caso, algo así será suficiente:

User.where('first_name LIKE ? OR last_name LIKE ?', 'Tobias', 'Fünke')
Tomasz Jaśkiewicz
fuente
15

He podido lograr esto, incluso en muchas situaciones extrañas, utilizando el Arel incorporado de Rails .

User.where(
  User.arel_table[:first_name].eq('Tobias').or(
    User.arel_table[:last_name].eq('Fünke')
  )
)

Esto combina ambas relaciones ActiveRecord utilizando Arel's o .


Combinar, como se sugirió aquí, no funcionó para mí. Se eliminó el segundo conjunto de objetos de relación de los resultados.

6 pies Dan
fuente
2
Esta es la mejor respuesta, OMI. Funciona perfectamente para consultas de tipo OR.
thekingoftruth
Gracias por señalar esto, me ayudó mucho. Las otras respuestas solo funcionan para un pequeño subconjunto de campos, pero para más de diez mil identificadores en la declaración IN, el rendimiento puede ser muy malo.
onetwopunch
1
@KeesBriggs, eso no es cierto. Lo he usado en todas las versiones de Rails 4.
6ft Dan
14

Hay una gema llamada active_record_union que podría ser lo que estás buscando.

Su ejemplo de uso es el siguiente:

current_user.posts.union(Post.published)
current_user.posts.union(Post.published).where(id: [6, 7])
current_user.posts.union("published_at < ?", Time.now)
user_1.posts.union(user_2.posts).union(Post.published)
user_1.posts.union_all(user_2.posts)
eco
fuente
Gracias por compartir. Incluso cuatro años después de su publicación, esta ha sido la única solución para mí para crear un sindicato ransacky acts-as-taggable-onmantener ActiveRecordintactas mis instancias.
Benjamin Bojko
Al usar esta gema, es posible que no obtenga un ActiveRecord :: Relation devuelto por la llamada union () si tiene ámbitos definidos en el modelo. Vea State of the Union en ActiveRecord en el archivo Léame.
Dechimp
9

Así es como lo "manejé" si usas pluck para obtener un identificador para cada uno de los registros, unir las matrices y finalmente hacer una consulta para esos identificadores unidos:

  transaction_ids = @club.type_a_trans.pluck(:id) + @club.type_b_transactions.pluck(:id) + @club.type_c_transactions.pluck(:id)
  @transactions = Transaction.where(id: transaction_ids).limit(100)
daveomcd
fuente
6

Si tiene una serie de relaciones de registro activas y desea fusionarlas todas, puede hacer

array.inject(:merge)
stevenspiel
fuente
array.inject(:merge)no funcionó para un conjunto de relaciones de registro activo en rails 5.1.4. Pero lo array.flatten!hizo.
zhisme
0

Fuerza bruta:

first_name_relation = User.where(:first_name => 'Tobias') # ActiveRecord::Relation
last_name_relation  = User.where(:last_name  => 'Fünke') # ActiveRecord::Relation

all_name_relations = User.none
first_name_relation.each do |ar|
  all_name_relations.new(ar)
end
last_name_relation.each do |ar|
  all_name_relations.new(ar)
end
Richard Grossman
fuente
"NoMethodError (método indefinido` stringify_keys 'para # <MyModel: 0x ...) en Rails3, incluso después de cambiar MyModel.none a MyModel.where (1 = 2), ya que Rails3 no tiene el método' none '.
JosephK