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.