Połącz dwa obiekty ActiveRecord :: Relation


174

Załóżmy, że mam następujące dwa obiekty:

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

czy można połączyć te dwie relacje w celu uzyskania jednego ActiveRecord::Relationobiektu zawierającego oba warunki?

Uwaga: zdaję sobie sprawę, że mogę połączyć miejsca, aby uzyskać takie zachowanie, naprawdę interesuje mnie przypadek, w którym mam dwa oddzielne ActiveRecord::Relationobiekty.


Odpowiedzi:


202

Jeśli chcesz połączyć używając AND(przecięcie), użyj merge:

first_name_relation.merge(last_name_relation)

Jeśli chcesz połączyć za pomocą OR(union), użyj :or

first_name_relation.or(last_name_relation)

Tylko w ActiveRecord 5+; dla 4.2 zainstaluj gdzie-lub backport.


a co z tym, że chcę, aby wynik był ActiveRecord::Relationtypem?
Nowa Aleksandria

1
@NewAlexandria Tak, we wszystkich przypadkach, Rails okłamują Cię co do klasy obiektu.
Andrew Marshall

77
Czy istnieje wersja scalania „LUB”?
Arcolye,

8
@minohimself Prawdopodobnie dlatego, że jest to rzeczywisty wynik połączenia twoich dwóch relacji. Zauważ, że mergejest to skrzyżowanie, a nie związek.
Andrew Marshall,

5
W przyszłości #ormetoda została dodana do ActiveRecord :: Relation w styczniu 2015 r. I będzie częścią Rails 5.0 , która zostanie udostępniona pod koniec 2015 r. Pozwala na użycie operatora OR do łączenia klauzul WHERE lub HAVING. Możesz kupić HEAD, jeśli potrzebujesz go przed oficjalną premierą. Zobacz Merge Pull Request # 16052 @Arcolye @AndrewMarshall @ Aldo-xoen-Giambelluca
Claudio Floreani

37

Obiekty relacji można konwertować na tablice. To neguje możliwość późniejszego użycia na nich jakichkolwiek metod ActiveRecord, ale nie musiałem. Ja to zrobiłem:

name_relation = first_name_relation + last_name_relation

Ruby 1.9, szyny 3.2


Aktualizacja: szkoda, że ​​to nie łączy się według daty
Daniel Morris

68
Nie działa jako tablica, konwertuje twoją relację na tablicę. Wtedy nie możesz już używać na nim metod ActiveRecord, takich jak .where () ...
Augustin Riedinger

1
Jeśli nie zależy Ci na tym, aby typ zwracany był relacją, możesz połączyć wiele wyników za pomocą argumentu first_name_relation | last_name_relation. „|” operator działa również na wielu relacjach. Wartość wynikowa to tablica.
MichaelZ

11
Pierwotne pytanie brzmiało: „czy można połączyć te dwie relacje w celu utworzenia jednego obiektu ActiveRecord :: Relation zawierającego oba warunki?” Ta odpowiedź zwraca tablicę ...
courimas

To DOSKONAŁE rozwiązanie w wielu przypadkach. Jeśli potrzebujesz tylko wyliczenia rekordów do wykonania pętli (np. Nie obchodzi cię, czy jest to Array czy ActiveRecord :: Relation) i nie przeszkadza ci narzut dwóch zapytań, rozwiązuje to problem dzięki oczywistej, prostej składni. Chciałbym móc +2 to!
David Hempy

21

mergewłaściwie nie działa jak OR. To po prostu przecięcie ( AND)

Zmagałem się z tym problemem, aby połączyć obiekty ActiveRecord :: Relation w jeden i nie znalazłem dla mnie działającego rozwiązania.

Zamiast szukać właściwej metody tworzenia unii z tych dwóch zbiorów, skupiłem się na algebrze zbiorów. Możesz to zrobić w inny sposób, korzystając z prawa De Morgana

ActiveRecord zapewnia metodę scalania (AND), a także możesz użyć not method lub none_of (NOT).

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

Masz tutaj (A u B) '= A' ^ B '

AKTUALIZACJA: powyższe rozwiązanie jest dobre w bardziej złożonych przypadkach. W twoim przypadku coś takiego wystarczy:

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

15

Udało mi się to osiągnąć, nawet w wielu dziwnych sytuacjach, używając wbudowanego Arel Railsów .

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

Spowoduje to scalenie obu relacji ActiveRecord przy użyciu metody Arel lub .


Merge, jak tu zasugerowano, nie działa dla mnie. Usunął z wyników drugi zestaw obiektów relacji.


2
To najlepsza odpowiedź, IMO. Działa bezbłędnie w przypadku zapytań typu OR.
thekingoftruth

Dziękuję za zwrócenie uwagi, bardzo mi pomogli. Inne odpowiedzi działają tylko dla niewielkiego podzbioru pól, ale dla ponad dziesięciu tysięcy identyfikatorów w górę w instrukcji IN wydajność może być bardzo niska.
onetwopunch

1
@KeesBriggs - to nieprawda. Użyłem tego we wszystkich wersjach Rails 4.
6 stóp Dan

14

Istnieje klejnot o nazwie active_record_union, który może być tym, czego szukasz.

Oto przykładowe zastosowania:

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)

Dzięki za udostępnienie. Nawet cztery lata po swoim poście to było jedynym rozwiązaniem dla mnie do utworzenia unii z ransacki acts-as-taggable-onutrzymując swoje ActiveRecordinstancje nienaruszone.
Benjamin Bojko

Używając tego klejnotu, możesz nie otrzymać ActiveRecord :: Relation zwróconego przez wywołanie union (), jeśli masz zdefiniowane zakresy w modelu. Zobacz State of the Union w ActiveRecord w pliku Readme.
dechimp

9

Oto jak sobie z tym poradziłem, jeśli użyjesz pluck, aby uzyskać identyfikator dla każdego z rekordów, połączyć tablice razem, a następnie wykonać zapytanie o te połączone identyfikatory:

  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

Jeśli masz tablicę aktywnych relacji rekordów i chcesz je wszystkie scalić, możesz to zrobić

array.inject(:merge)

array.inject(:merge)nie działał dla tablicy aktywnych relacji rekordów w szynach 5.1.4. Ale array.flatten!zrobił.
zhisme

0

Brute force it:

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 (undefined method` stringify_keys 'for # <MyModel: 0x ...) w Rails3, nawet po zmianie MyModel.none na MyModel.where (1 = 2), ponieważ Rails3 nie ma metody' none '.
JosephK
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.