Konwersja tablicy obiektów do ActiveRecord :: Relation


104

Mam tablicę obiektów, nazwijmy to Indicator. Chcę uruchomić metody klasy Indicator (te z def self.subjectsróżnych, zakresów itp.) Na tej tablicy. Jedynym sposobem, w jaki znam, aby uruchamiać metody klas na grupie obiektów, jest posiadanie ich jako ActiveRecord :: Relation. W końcu uciekam się do dodania to_indicatorsmetody do Array.

def to_indicators
  # TODO: Make this less terrible.
  Indicator.where id: self.pluck(:id)
end

Czasami łączę kilka z tych zakresów w celu odfiltrowania wyników w ramach metod klasowych. Tak więc, mimo że wywołuję metodę na ActiveRecord :: Relation, nie wiem, jak uzyskać dostęp do tego obiektu. Do jego zawartości mogę dotrzeć tylko przez all. Ale alljest tablicą. Więc muszę przekonwertować tę tablicę na ActiveRecord :: Relation. Na przykład jest to część jednej z metod:

all.to_indicators.applicable_for_bank(id).each do |indicator|
  total += indicator.residual_risk_for(id)
  indicator_count += 1 if indicator.completed_by?(id)
end

Myślę, że to sprowadza się do dwóch pytań.

  1. Jak mogę przekonwertować Array obiektów na ActiveRecord :: Relation? Najlepiej bez robienia za wherekażdym razem.
  2. Podczas uruchamiania def self.subjectsmetody typu na ActiveRecord :: Relation, w jaki sposób mogę uzyskać dostęp do samego obiektu ActiveRecord :: Relation?

Dzięki. Jeśli muszę coś wyjaśnić, daj mi znać.


3
Jeśli jedynym powodem, dla którego próbujesz przekonwertować tę tablicę z powrotem na relację, jest to, że otrzymałeś ją przez .all, po prostu użyj .scopedtak, jak wskazuje odpowiedź Andrew Marshalla (Chociaż w rails 4 będzie działać .all). Jeśli okaże się, że musisz zamienić tablicę w relację, gdzieś popełniłeś błąd ...
— nzifnab

Odpowiedzi:


46

Jak mogę przekonwertować Array obiektów na ActiveRecord :: Relation? Najlepiej bez robienia gdzie za każdym razem.

Nie można przekonwertować Array na ActiveRecord :: Relation, ponieważ Relation jest tylko konstruktorem zapytania SQL, a jego metody nie działają na rzeczywistych danych.

Jeśli jednak chcesz mieć relację, to:

  • dla ActiveRecord 3.x, nie wywołuj alli zamiast tego wywołaj scoped, co zwróci relację, która reprezentuje te same rekordy, allktóre dałyby ci w Array.

  • dla ActiveRecord 4.x, po prostu wywołaj all, co zwróci Relację.

Podczas uruchamiania def self.subjectsmetody typu na ActiveRecord :: Relation, w jaki sposób mogę uzyskać dostęp do samego obiektu ActiveRecord :: Relation?

Gdy metoda jest wywoływana na obiekcie Relation, selfjest to relacja (w przeciwieństwie do klasy modelu, w której jest zdefiniowana).


1
Zobacz @Marco Prins poniżej, aby zapoznać się z rozwiązaniem.
— Justin,

co z przestarzałą metodą
— .push w railsach

@GstjiSaini Nie jestem pewien, do jakiej metody się odnosisz, podaj dokument lub link do źródła. Chociaż, jeśli jest przestarzały, nie jest to bardzo realne rozwiązanie, ponieważ prawdopodobnie wkrótce zniknie.
— Andrew Marshall,

I dlatego można to zrobić class User; def self.somewhere; where(location: "somewhere"); end; end, a następnieUser.limit(5).somewhere
— Dorian

To jedyna odpowiedź, która naprawdę oświeca PO. Pytanie ujawnia wadliwą wiedzę o tym, jak działa ActiveRecord. @Justin tylko wzmacnia brak zrozumienia, dlaczego przekazywanie tablic obiektów tylko po to, aby je zmapować i zbudować kolejne niepotrzebne zapytanie, jest złe.
— james2m

162

Możesz przekonwertować tablicę obiektów arrna ActiveRecord :: Relation w ten sposób (zakładając, że wiesz, jaką klasą są obiekty, co prawdopodobnie robisz)

MyModel.where(id: arr.map(&:id))

Musisz jednak użyć where, jest to przydatne narzędzie, którego nie powinieneś niechętnie używać. Teraz masz jednowierszowy konwertujący tablicę na relację.

map(&:id)zamieni twoją tablicę obiektów w tablicę zawierającą tylko ich identyfikatory. A przekazanie tablicy do klauzuli where wygeneruje instrukcję SQL, INktóra wygląda mniej więcej tak:

SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....

Pamiętaj, że uporządkowanie tablicy zostanie utracone - ale ponieważ Twoim celem jest tylko uruchomienie metody klasy na kolekcji tych obiektów, zakładam, że nie będzie to problem.


3
Dobra robota, dokładnie to, czego potrzebowałem. Możesz tego użyć dla dowolnego atrybutu w modelu. u mnie działa idealnie.
— nfriend21

7
Po co samodzielnie tworzyć dosłowne SQL, skoro możesz where(id: arr.map(&:id))? Ściśle mówiąc, nie konwertuje to tablicy na relację, ale zamiast tego pobiera nowe instancje obiektów (po zrealizowaniu relacji) z tymi identyfikatorami, które mogą mieć inne wartości atrybutów niż instancje znajdujące się już w pamięci w tablicy.
— Andrew Marshall

7
To jednak traci porządek.
— Velizar Hristov

1
@VelizarHristov To dlatego, że jest to teraz relacja, którą można uporządkować tylko według kolumny, a nie w dowolny sposób. Relacje są szybsze w obsłudze dużych zestawów danych i będą pewne kompromisy.
— Marco Prins

8
Bardzo nieefektywne! Zamieniłeś kolekcję obiektów, które już masz w pamięci, w te, do których masz zamiar uzyskać dostęp do bazy danych. Spojrzałbym na refaktoryzację tych metod klas, które chcesz iterować po tablicy.
— james2m

5

Cóż, w moim przypadku muszę przekonwertować tablicę obiektów na ActiveRecord :: Relation, a także posortować je według określonej kolumny (na przykład id). Ponieważ używam MySQL, funkcja pola może być pomocna.

MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})") 

SQL wygląda następująco:

SELECT ... FROM ... WHERE (id in (11,5,6,7,8,9,10))  
ORDER BY field(id,11,5,6,7,8,9,10)

Funkcja pola MySQL


Wersja, która działa z PostgreSQL, nie szukaj dalej niż ten wątek: stackoverflow.com/questions/1309624/ ...
— armchairdj

0

ActiveRecord::Relation wiąże zapytanie bazy danych, które pobiera dane z bazy danych.

Załóżmy, że ma sens, mamy tablicę z obiektami tej samej klasy, a następnie z jakim zapytaniem mamy je powiązać?

Kiedy biegnę,

users = User.where(id: [1,3,4,5])
  User Load (0.6ms)  SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc

Tutaj, powyżej, userszwróć Relationobiekt, ale wiąże zapytanie bazy danych za nim i możesz go wyświetlić,

users.to_sql
 => "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5)  ORDER BY created_at desc"

Nie ma więc możliwości powrotu ActiveRecord::Relationz tablicy obiektów, która jest niezależna od zapytania sql.


0

Przede wszystkim NIE jest to srebrna kula. Z mojego doświadczenia odkryłem, że konwersja do relacji jest czasami łatwiejsza niż alternatywy. Staram się stosować to podejście bardzo oszczędnie i tylko w przypadkach, gdy alternatywa byłaby bardziej złożona.

Biorąc to pod uwagę, jest to moje rozwiązanie, rozszerzyłem Arrayklasę

# lib/core_ext/array.rb

class Array

  def to_activerecord_relation
    return ApplicationRecord.none if self.empty?

    clazzes = self.collect(&:class).uniq
    raise 'Array cannot be converted to ActiveRecord::Relation since it does not have same elements' if clazzes.size > 1

    clazz = clazzes.first
    raise 'Element class is not ApplicationRecord and as such cannot be converted' unless clazz.ancestors.include? ApplicationRecord

    clazz.where(id: self.collect(&:id))
  end
end

Przykładem użycia może być array.to_activerecord_relation.update_all(status: 'finished'). Gdzie go teraz użyć?

Czasami trzeba odfiltrować, ActiveRecord::Relationna przykład wyjąć nieukończone elementy. W takich przypadkach najlepiej jest użyć zasięgu elements.not_finishedi nadal zachować ActiveRecord::Relation.

Ale czasami ten stan jest bardziej złożony. Wyjmij wszystkie elementy, które nie są ukończone, a które zostały wyprodukowane w ciągu ostatnich 4 tygodni i zostały sprawdzone. Aby uniknąć tworzenia nowych zakresów, możesz filtrować do tablicy, a następnie konwertować z powrotem. Pamiętaj, że nadal wykonujesz zapytanie do DB, szybko, ponieważ wyszukuje on, idale nadal jest zapytaniem.

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.