Combine dois objetos ActiveRecord :: Relation


174

Suponha que eu tenha os dois objetos a seguir:

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

é possível combinar as duas relações para produzir um ActiveRecord::Relationobjeto contendo as duas condições?

Nota: Estou ciente de que posso encadear os caminhos para obter esse comportamento. O que realmente me interessa é o caso em que tenho dois ActiveRecord::Relationobjetos separados .


Respostas:


202

Se você deseja combinar usando AND(interseção), use merge:

first_name_relation.merge(last_name_relation)

Se você deseja combinar usando OR(união), use † :or

first_name_relation.or(last_name_relation)

† Somente no ActiveRecord 5+; para 4.2 instale o where-ou backport.


Que tal se eu quiser que o resultado seja um ActiveRecord::Relationtipo?
— New Alexandria

1
@NewAlexandria Sim, em todos os casos, o Rails está mentindo para você sobre a classe do objeto.
— Andrew Marshall

77
Existe uma versão "OU" de mesclagem?
— Arcolye

8
@minohimself Provavelmente porque esse é o resultado real da fusão de suas duas relações. Observe que mergeé uma interseção, não união.
— Andrew Marshall

5
Para referência futura, o #ormétodo foi adicionado ao ActiveRecord :: Relation em janeiro de 2015 e fará parte do Rails 5.0 , que será lançado no final de 2015. Ele permite o uso do operador OR para combinar cláusulas WHERE ou HAVING. Você pode fazer o checkout do HEAD se precisar antes do lançamento oficial. Veja a solicitação de mesclagem # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
— Claudio Floreani

37

Objetos de relação podem ser convertidos em matrizes. Isso nega a possibilidade de usar qualquer método ActiveRecord neles posteriormente, mas eu não precisava. Eu fiz isso:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, trilhos 3.2


Update: pena isto não merge por data
— Daniel Morris

68
Ele não atua como uma matriz, converte sua relação em uma matriz. Então você não pode usar métodos ActiveRecord nele como .onde () mais ...
— Augustin Riedinger

1
Se você não se importa com o tipo de retorno sendo relação, é possível combinar vários resultados com first_name_relation | last_name_relation. O "|" O operador trabalha em várias relações também. O valor do resultado é uma matriz.
— MichaelZ

11
A pergunta original era: "é possível combinar as duas relações para produzir um objeto ActiveRecord :: Relation contendo as duas condições?" Esta resposta retorna uma matriz ...
— tribunalimas

Esta é uma solução EXCELENTE para muitos casos. Se você precisar apenas de uma enumeração de registros para percorrer (por exemplo, você não se importa se é uma matriz ou um ActiveRecord :: Relation) e não se importa com a sobrecarga de duas consultas, isso resolve o problema com uma sintaxe simples e óbvia. Gostaria de poder +2!
— David Hempy 29/08

21

mergena verdade não funciona como OR. É simplesmente interseção (AND )

Lutei com esse problema para combinar os objetos ActiveRecord :: Relation em um e não encontrei nenhuma solução funcional para mim.

Em vez de procurar o método certo para criar uma união a partir desses dois conjuntos, concentrei-me na álgebra de conjuntos. Você pode fazer isso de maneira diferente usando a lei de De Morgan

O ActiveRecord fornece o método de mesclagem (AND) e você também não pode usar o método ou none_of (NOT).

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

Você tem aqui (A u B) '= A' ^ B '

ATUALIZAÇÃO: A solução acima é adequada para casos mais complexos. No seu caso, basta isso:

User.where('first_name LIKE ? OR last_name LIKE ?', 'Tobias', 'Fünke')

15

Consegui fazer isso, mesmo em muitas situações estranhas, usando o Arel interno do Rails .

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

Isso mescla as relações ActiveRecord usando Arel ou .


A mesclagem, como foi sugerido aqui, não funcionou para mim. Ele retirou o segundo conjunto de objetos de relação dos resultados.


2
Esta é a melhor resposta, IMO. Funciona perfeitamente para consultas do tipo OR.
— thekingoftruth

Obrigado por apontar isso, me ajudou muito. As outras respostas funcionam apenas para um pequeno subconjunto de campos, mas para mais de dez mil IDs na instrução IN, o desempenho pode ser muito ruim.
— Onetwopunch

1
@KeesBriggs - isso não é verdade. Eu usei isso em todas as versões do Rails 4.
— 6ft Dan

14

Existe uma gema chamada active_record_union que pode ser o que você está procurando.

Seu exemplo de uso é o seguinte:

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)

Obrigado por compartilhar. Mesmo quatro anos após o seu post, essa foi a única solução para eu criar uma união ransacke acts-as-taggable-onmanter ActiveRecordintactas minhas instâncias.
— Benjamin Bojko

Ao usar esta gema, você pode não receber uma ActiveRecord :: Relation retornada pela chamada union () se você tiver escopos definidos no modelo. Consulte Estado da união no ActiveRecord no Leia-me.
— Dechimp

9

Foi assim que eu "lidei" com isso, se você usar o pluck para obter um identificador para cada um dos registros, juntar as matrizes e, finalmente, fazer uma consulta para esses IDs:

  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)

6

Se você tiver uma série de relações de registros de ativos e quiser mesclar todas elas, poderá fazer

array.inject(:merge)

array.inject(:merge)não funcionou para uma série de relações de registros de ativos nos trilhos 5.1.4. Mas array.flatten!fez.
— zhisme

0

Força 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

"NoMethodError (método indefinido` stringify_keys 'para # <MyModel: 0x ...) no Rails3, mesmo após alterar o MyModel.none para MyModel.where (1 = 2), pois o Rails3 não possui o método' none '.
— JosephK
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.