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 .

Patrick Klingemann
fonte
Pode ser de ajuda: ActiveRecord OR query notação Hash
potashin

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.

Andrew Marshall
fonte
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

que meio caminho
fonte
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')
Tomasz Jaśkiewicz
fonte
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.

6ft Dan
fonte
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)
eco
fonte
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)
daveomcd
fonte
6

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

array.inject(:merge)
Stevenspiel
fonte
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
Richard Grossman
fonte
"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