Combina due oggetti ActiveRecord :: Relation


174

Supponiamo che io abbia i seguenti due oggetti:

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

è possibile combinare le due relazioni per produrre un ActiveRecord::Relationoggetto contenente entrambe le condizioni?

Nota: sono consapevole che posso concatenare dove trovare questo comportamento, quello che mi interessa davvero è il caso in cui ho due ActiveRecord::Relationoggetti separati .


Risposte:


202

Se si desidera combinare utilizzando AND(intersezione), utilizzare merge:

first_name_relation.merge(last_name_relation)

Se vuoi combinare usando OR(unione), usa :or

first_name_relation.or(last_name_relation)

Solo in ActiveRecord 5+; per 4.2 installare il where-or backport.


che ne dici se voglio che il risultato sia un ActiveRecord::Relationtipo?
Nuova Alessandria,

1
@NewAlexandria Sì, tutti i casi, Rails ti sta mentendo sulla classe dell'oggetto.
Andrew Marshall,

77
Esiste una versione "OR" di unione?
Arcolye,

8
@minohimself Probabilmente perché questo è il risultato reale della fusione delle tue due relazioni. Nota che mergeè un incrocio, non unione.
Andrew Marshall,

5
Per riferimento futuro, il #ormetodo è stato aggiunto ad ActiveRecord :: Relation a gennaio 2015 e farà parte di Rails 5.0 , che verrà distribuito alla fine del 2015. Permette l'uso dell'operatore OR per combinare clausole WHERE o HAVING. Puoi controllare HEAD se ne hai bisogno prima del rilascio ufficiale. Vedi Merge Pull Request # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Claudio Floreani

37

Gli oggetti relazione possono essere convertiti in matrici. Ciò nega di essere in grado di utilizzare qualsiasi metodo ActiveRecord in seguito, ma non è stato necessario. L'ho fatto:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, binari 3.2


Aggiornamento: peccato che questo non si fonda per data
Daniel Morris,

68
Non agisce come un array, converte la tua relazione in un array. Quindi non puoi più usare i metodi ActiveRecord come .where () ...
Augustin Riedinger,

1
Se non ti interessa che il tipo restituito sia relazione, puoi combinare più risultati con first_name_relation | last_name_relation. Il "|" l'operatore lavora anche su più relazioni. Il valore del risultato è un array.
MichaelZ,

11
La domanda originale era: "è possibile combinare le due relazioni per produrre un oggetto ActiveRecord :: Relation contenente entrambe le condizioni?" Questa risposta restituisce una matrice ...
cortei

Questa è una soluzione ECCELLENTE per molti casi. Se hai solo bisogno di un elenco di record da scorrere (ad esempio non ti importa se si tratta di un array o di un ActiveRecord :: Relation) e non ti preoccupare del sovraccarico di due query, questo risolve il problema con una sintassi ovvia e semplice. Vorrei poterlo fare +2!
David Hempy,

21

mergein realtà non funziona come OR. È semplicemente intersezione ( AND)

Ho lottato con questo problema per combinare gli oggetti ActiveRecord :: Relation in uno e non ho trovato alcuna soluzione funzionante per me.

Invece di cercare il metodo giusto per creare un'unione da questi due insiemi, mi sono concentrato sull'algebra degli insiemi. Puoi farlo in modo diverso usando la legge di De Morgan

ActiveRecord fornisce il metodo merge (AND) e inoltre puoi usare not method o none_of (NOT).

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

Hai qui (A u B) '= A' ^ B '

AGGIORNAMENTO: la soluzione sopra è buona per casi più complessi. Nel tuo caso basta così:

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

15

Sono stato in grado di ottenere questo risultato, anche in molte situazioni strane, usando Arel integrato di Rails .

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

Questo unisce entrambe le relazioni ActiveRecord usando Arel o .


Unire, come è stato suggerito qui, non ha funzionato per me. Ha eliminato la seconda serie di oggetti relazione dai risultati.


2
Questa è la risposta migliore, IMO. Funziona perfettamente per le query di tipo OR.
thekingoftruth

Grazie per averlo sottolineato, mi ha aiutato molto. Le altre risposte funzionano solo per un piccolo sottoinsieme di campi, ma per oltre diecimila ID nell'istruzione IN, le prestazioni possono essere pessime.
onetwopunch

1
@KeesBriggs: non è vero. L'ho usato in tutte le versioni di Rails 4.
6ft Dan,

14

C'è una gemma chiamata active_record_union che potrebbe essere quello che stai cercando.

Il suo esempio di utilizzo è il seguente:

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)

Grazie per la condivisione. Anche quattro anni dopo il tuo post, questa è stata l'unica soluzione per me per creare un sindacato ransacke acts-as-taggable-onmantenere ActiveRecordintatte le mie istanze.
Benjamin Bojko,

Quando si utilizza questa gemma, è possibile che non si ottenga una ActiveRecord :: Relation restituita dalla chiamata union () se nel modello sono definiti ambiti. Vedi State of the Union in ActiveRecord nel file Leggimi.
dechimp

9

È così che l'ho "gestito" se usi pluck per ottenere un identificatore per ciascuno dei record, unire le matrici insieme e infine eseguire una query per quegli ID uniti:

  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 hai una serie di relazioni activerecord e vuoi unirle tutte, puoi farlo

array.inject(:merge)

array.inject(:merge)non ha funzionato per la matrice di relazioni activerecord nelle rotaie 5.1.4. Ma ha array.flatten!fatto.
Zhisme,

0

Forza 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 (metodo non definito` stringify_keys 'per # <MyModel: 0x ...) in Rails3, anche dopo aver cambiato MyModel.none in MyModel.where (1 = 2), poiché Rails3 non ha il metodo' none '.
JosephK,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.