Kombinieren Sie zwei ActiveRecord :: Relation-Objekte

174

Angenommen, ich habe die folgenden zwei Objekte:

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

Ist es möglich, die beiden Beziehungen zu kombinieren, um ein ActiveRecord::RelationObjekt zu erzeugen, das beide Bedingungen enthält?

Hinweis: Mir ist bewusst, dass ich die Orte verketten kann, an denen dieses Verhalten auftritt. Was mich wirklich interessiert, ist der Fall, in dem ich zwei separate ActiveRecord::RelationObjekte habe.

Patrick Klingemann
quelle
Kann

Antworten:

202

Wenn Sie mit AND(Schnittpunkt) kombinieren möchten , verwenden Sie merge:

first_name_relation.merge(last_name_relation)

Wenn Sie mit OR(union) kombinieren möchten , verwenden Sie :or

first_name_relation.or(last_name_relation)

Nur in ActiveRecord 5+; Installieren Sie für 4.2 den Where- oder Backport.

Andrew Marshall
quelle
Wie wäre es, wenn das Ergebnis ein ActiveRecord::RelationTyp sein soll?
New Alexandria
1
@NewAlexandria Ja, in allen Fällen belügt Rails Sie wegen der Klasse des Objekts.
Andrew Marshall
77
Gibt es eine "ODER" -Version der Zusammenführung?
Arcolye
8
@minohimself Wahrscheinlich, weil dies das eigentliche Ergebnis der Zusammenführung Ihrer beiden Beziehungen ist. Beachten Sie, dass dies mergeeine Kreuzung ist, keine Vereinigung.
Andrew Marshall
5
Zum späteren Nachschlagen wurde die #orMethode im Januar 2015 zu ActiveRecord :: Relation hinzugefügt und wird Teil von Rails 5.0 sein , das Ende 2015 ausgeliefert wird. Sie ermöglicht die Verwendung des Operators OR, um WHERE- oder HAVING-Klauseln zu kombinieren. Sie können HEAD vor der offiziellen Veröffentlichung auschecken, wenn Sie es benötigen. Siehe Merge Pull Request # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Claudio Floreani
37

Beziehungsobjekte können in Arrays konvertiert werden. Dies negiert die Möglichkeit, anschließend ActiveRecord-Methoden für sie verwenden zu können, dies war jedoch nicht erforderlich. Ich war das:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, Schienen 3.2

das Mittelweg
quelle
Update: Schade, dass dies nicht nach Datum verschmilzt
Daniel Morris
68
Es fungiert nicht als Array, sondern konvertiert Ihre Beziehung in ein Array. Dann können Sie keine ActiveRecord-Methoden mehr wie .where () verwenden ...
Augustin Riedinger
1
Wenn Sie nicht möchten, dass der Rückgabetyp eine Beziehung ist, können Sie mehrere Ergebnisse mit first_name_relation | kombinieren last_name_relation. Das "|" Der Operator arbeitet auch mit mehreren Beziehungen. Der Ergebniswert ist ein Array.
MichaelZ
11
Die ursprüngliche Frage lautete: "Ist es möglich, die beiden Relationen zu einem ActiveRecord :: Relation-Objekt zu kombinieren, das beide Bedingungen enthält?" Diese Antwort gibt ein Array zurück ...
Courtsimas
Dies ist in vielen Fällen eine AUSGEZEICHNETE Lösung. Wenn Sie nur eine Aufzählung von Datensätzen zum Durchlaufen benötigen (z. B. ist es Ihnen egal, ob es sich um ein Array oder eine ActiveRecord :: Relation handelt) und sich nicht um den Aufwand von zwei Abfragen kümmern, löst dies das Problem mit einer offensichtlichen, einfachen Syntax. Ich wünschte, ich könnte +2!
David Hempy
21

mergefunktioniert eigentlich nicht so OR. Es ist einfach Kreuzung ( AND)

Ich hatte Probleme mit diesem Problem, ActiveRecord :: Relation-Objekte zu einem zu kombinieren, und fand keine funktionierende Lösung für mich.

Anstatt nach der richtigen Methode zu suchen, um aus diesen beiden Mengen eine Vereinigung zu erstellen, konzentrierte ich mich auf die Algebra der Mengen. Sie können dies auf unterschiedliche Weise nach dem Gesetz von De Morgan tun

ActiveRecord bietet die Zusammenführungsmethode (AND) und Sie können auch not method oder none_of (NOT) verwenden.

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

Sie haben hier (A u B) '= A' ^ B '

UPDATE: Die obige Lösung eignet sich für komplexere Fälle. In Ihrem Fall wird so etwas ausreichen:

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

Ich habe dies auch in vielen seltsamen Situationen mit Rails 'integriertem Arel erreicht .

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

Dadurch werden beide ActiveRecord-Beziehungen mithilfe von oder von Arel zusammengeführt .


Das Zusammenführen hat, wie hier vorgeschlagen, bei mir nicht funktioniert. Der zweite Satz von Beziehungsobjekten wurde aus den Ergebnissen entfernt.

6ft Dan
quelle
2
Dies ist die beste Antwort, IMO. Es funktioniert einwandfrei für Abfragen vom Typ OR.
thekingoftruth
Vielen Dank für den Hinweis, hat mir sehr geholfen. Die anderen Antworten funktionieren nur für eine kleine Teilmenge von Feldern, aber für mehr als zehntausend IDs in der IN-Anweisung kann die Leistung sehr schlecht sein.
Onetwopunch
1
@KeesBriggs - das ist nicht wahr. Ich habe dies in allen Versionen von Rails 4 verwendet.
6ft Dan
14

Es gibt ein Juwel namens active_record_union , das genau das ist, wonach Sie suchen.

Das Beispiel für die Verwendung lautet wie folgt:

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)
Echo
quelle
Danke für das Teilen. Selbst vier Jahre nach Ihrem Beitrag war dies die einzige Lösung für mich, um eine Union aus ransackund acts-as-taggable-onunter Beibehaltung meiner ActiveRecordInstanzen zu schaffen .
Benjamin Bojko
Wenn Sie dieses Juwel verwenden, wird möglicherweise keine ActiveRecord :: Relation vom Aufruf von union () zurückgegeben, wenn für das Modell Bereiche definiert sind. Siehe Status der Union in ActiveRecord in der Readme-Datei.
Dechimp
9

So habe ich es "gehandhabt", wenn Sie mit "Zupfen" eine Kennung für jeden der Datensätze erhalten, die Arrays zusammenfügen und schließlich eine Abfrage für diese verknüpften IDs durchführen:

  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
quelle
6

Wenn Sie über eine Reihe von Aktivaufzeichnungsbeziehungen verfügen und alle zusammenführen möchten, können Sie dies tun

array.inject(:merge)
Stevenspiel
quelle
array.inject(:merge)funktionierte nicht für eine Reihe von Aktivaufzeichnungsbeziehungen in Schienen 5.1.4. Aber array.flatten!tat es.
Zhisme
0

Brute Force es:

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
quelle
"NoMethodError (undefinierte Methode` stringify_keys 'für # <MyModel: 0x ...) in Rails3, auch nach dem Ändern von MyModel.none in MyModel.where (1 = 2), da Rails3 nicht über die Methode' none 'verfügt.
JosephK