Konvertieren eines Array von Objekten in ActiveRecord :: Relation

104

Ich habe eine Reihe von Objekten, nennen wir es ein Indicator. Ich möchte Indicator-Klassenmethoden (die der def self.subjectsSorte, Bereiche usw.) für dieses Array ausführen . Die einzige Möglichkeit, Klassenmethoden für eine Gruppe von Objekten auszuführen, besteht darin, dass sie eine ActiveRecord :: Relation sind. Am Ende greife ich auf das Hinzufügen einer to_indicatorsMethode zurück Array.

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

Manchmal verkette ich einige dieser Bereiche, um die Ergebnisse innerhalb der Klassenmethoden herauszufiltern. Obwohl ich eine Methode für eine ActiveRecord :: Relation aufrufe, weiß ich nicht, wie ich auf dieses Objekt zugreifen soll. Ich kann nur durch zum Inhalt kommen all. Ist allaber ein Array. Dann muss ich dieses Array in eine ActiveRecord :: Relation konvertieren. Dies ist beispielsweise Teil einer der folgenden Methoden:

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

Ich denke, dies verdichtet sich auf zwei Fragen.

  1. Wie kann ich ein Array von Objekten in eine ActiveRecord :: Relation konvertieren? Am besten ohne wherejedes Mal.
  2. def self.subjectsWie greife ich beim Ausführen einer Typmethode auf einer ActiveRecord :: Relation auf dieses ActiveRecord :: Relation-Objekt selbst zu?

Vielen Dank. Wenn ich etwas klarstellen muss, lass es mich wissen.

Nathan
quelle
3
Wenn Ihr einziger Grund für den Versuch, dieses Array wieder in eine Beziehung zu konvertieren, darin besteht, dass Sie es über erhalten haben .all, verwenden Sie es einfach .scopedwie in Andrew Marshalls Antwort angegeben (obwohl es in Schienen 4 funktioniert .all). Wenn Sie feststellen, dass Sie ein Array in eine Beziehung verwandeln müssen, sind Sie irgendwo falsch gelaufen ...
nzifnab

Antworten:

46

Wie kann ich ein Array von Objekten in eine ActiveRecord :: Relation konvertieren? Am besten ohne jedes Mal ein Wo zu machen.

Sie können ein Array nicht in eine ActiveRecord :: Relation konvertieren, da eine Relation nur ein Builder für eine SQL-Abfrage ist und ihre Methoden nicht mit tatsächlichen Daten arbeiten.

Wenn Sie jedoch eine Beziehung wünschen, dann:

  • Rufen Sie für ActiveRecord 3.x nicht auf allund rufen Sie stattdessen auf. Dadurch scopedwird eine Relation zurückgegeben, die dieselben Datensätze darstellt, alldie Sie in einem Array erhalten würden.

  • Rufen Sie für ActiveRecord 4.x einfach auf all, wodurch eine Beziehung zurückgegeben wird.

def self.subjectsWie greife ich beim Ausführen einer Typmethode auf einer ActiveRecord :: Relation auf dieses ActiveRecord :: Relation-Objekt selbst zu?

Wenn die Methode für ein Relation-Objekt aufgerufen wird, selfist dies die Relation (im Gegensatz zu der Modellklasse, in der sie definiert ist).

Andrew Marshall
quelle
1
Informationen zur Lösung finden Sie unten unter @Marco Prins.
Justin
Was ist mit .push veraltete Methode in Schienen 5
Jaswinder
@GstjiSaini Ich bin mir nicht sicher, auf welche Methode Sie sich beziehen. Bitte geben Sie das Dokument oder den Quelllink an. Wenn es jedoch veraltet ist, ist es keine sehr praktikable Lösung, da es wahrscheinlich bald verschwinden wird.
Andrew Marshall
Und das ist , warum Sie tun können class User; def self.somewhere; where(location: "somewhere"); end; end, dannUser.limit(5).somewhere
Dorian
Dies ist die einzige Antwort, die das OP wirklich aufklärt. Die Frage zeigt fehlerhafte Kenntnisse über die Funktionsweise von ActiveRecord. @Justin verstärkt nur das Unverständnis darüber, warum es schlecht ist, weiterhin Arrays von Objekten herumzugeben, nur um sie zuzuordnen und eine weitere unnötige Abfrage zu erstellen.
James2m
161

Sie können ein Array von Objekten arrwie folgt in eine ActiveRecord :: Relation konvertieren (vorausgesetzt, Sie wissen, welche Klasse die Objekte sind, was Sie wahrscheinlich tun).

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

Sie müssen es jedoch verwenden where, es ist ein nützliches Werkzeug, das Sie nicht ungern verwenden sollten. Und jetzt haben Sie einen Einzeiler, der ein Array in eine Beziehung konvertiert.

map(&:id)verwandelt Ihr Array von Objekten in ein Array, das nur deren IDs enthält. Wenn Sie ein Array an eine where-Klausel übergeben, wird eine SQL-Anweisung generiert IN, die ungefähr so ​​aussieht:

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

Denken Sie daran, dass die Reihenfolge des Arrays verloren geht. Da Ihr Ziel jedoch nur darin besteht, eine Klassenmethode für die Sammlung dieser Objekte auszuführen , gehe ich davon aus, dass dies kein Problem darstellt.

Marco Prins
quelle
3
Gut gemacht, genau das, was ich brauchte. Sie können dies für jedes Attribut im Modell verwenden. funktioniert perfekt für mich.
nfriend21
7
Warum selbst wörtliches SQL erstellen, wenn Sie dies können where(id: arr.map(&:id))? Genau genommen konvertiert dies das Array nicht in eine Beziehung, sondern ruft stattdessen neue Instanzen der Objekte (sobald die Beziehung realisiert ist) mit den IDs ab, die möglicherweise andere Attributwerte als die bereits im Speicher befindlichen Instanzen im Array haben.
Andrew Marshall
7
Dies verliert jedoch die Bestellung.
Velizar Hristov
1
@VelizarHristov Das liegt daran, dass es sich jetzt um eine Beziehung handelt, die nur nach einer Spalte geordnet werden kann und nicht nach Ihren Wünschen. Beziehungen sind schneller für die Verarbeitung großer Datenmengen, und es wird einige Kompromisse geben.
Marco Prins
8
Sehr ineffizient! Sie haben eine Sammlung von Objekten, die Sie bereits im Speicher haben, in Objekte umgewandelt, auf die Sie eine Datenbankabfrage durchführen, um darauf zuzugreifen. Ich würde versuchen, die Klassenmethoden zu überarbeiten, die Sie über das Array iterieren möchten.
James2m
5

Nun, in meinem Fall, ich brauche , um ein Array von Objekten zu Active Umwandlung :: Relation sowie Sortier- sie mit einer bestimmten Spalte (id zum Beispiel). Da ich MySQL verwende, kann die Feldfunktion hilfreich sein.

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

Das SQL sieht aus wie:

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

MySQL-Feldfunktion

Xingcheng Xia
quelle
Für eine Version davon , die mit PostgreSQL, nicht weiter als dieser Thread funktioniert: stackoverflow.com/questions/1309624/...
armchairdj
0

ActiveRecord::Relation bindet eine Datenbankabfrage, die Daten aus der Datenbank abruft.

Nehmen wir an, wir haben ein Array mit Objekten derselben Klasse. Mit welcher Abfrage sollen wir sie dann binden?

Wenn ich renne,

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

Hier oben, usersRückkehr RelationObjekt , sondern bindet Datenbankabfrage dahinter und man kann es sehen,

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

Es ist daher nicht möglich, ActiveRecord::Relationvon einem Array von Objekten zurückzukehren, das von der SQL-Abfrage unabhängig ist.

Strahl
quelle
0

Erstens ist dies KEINE Silberkugel. Aufgrund meiner Erfahrung stellte ich fest, dass die Konvertierung in eine Beziehung manchmal einfacher ist als Alternativen. Ich versuche, diesen Ansatz sehr sparsam und nur in Fällen anzuwenden, in denen die Alternative komplexer wäre.

Davon abgesehen ist meine Lösung, ich habe den ArrayUnterricht erweitert

# 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

Ein Anwendungsbeispiel wäre array.to_activerecord_relation.update_all(status: 'finished'). Wo verwende ich es jetzt?

Manchmal müssen Sie ActiveRecord::Relationbeispielsweise nicht abgeschlossene Elemente herausfiltern. In diesen Fällen ist es am besten, den Umfang zu verwenden, elements.not_finishedund Sie würden ihn trotzdem behalten ActiveRecord::Relation.

Aber manchmal ist dieser Zustand komplexer. Nehmen Sie alle Elemente heraus, die noch nicht fertig sind und die in den letzten 4 Wochen hergestellt und inspiziert wurden. Um das Erstellen neuer Bereiche zu vermeiden, können Sie in ein Array filtern und dann zurück konvertieren. Denken Sie daran, dass Sie immer noch eine Abfrage an DB durchführen, schnell, da diese nach ideiner Abfrage sucht .

Haris Krajina
quelle