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ć.

Nathan
źródło
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).

Andrew Marshall
źródło
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.

Marco Prins
źródło
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

Xingcheng Xia
źródło
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.

promień
źródło
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.

Haris Krajina
źródło