Ich habe ein Array von Objekten, nennen wir es eine Indicator
. Ich möchte Indicator-Klassenmethoden (die der def self.subjects
-Sorte, Gültigkeitsbereiche usw.) für dieses Array ausführen. Die einzige Möglichkeit, Klassenmethoden für eine Gruppe von Objekten auszuführen, besteht darin, sie als ActiveRecord :: Relation zu verwenden. Am Ende greife ich darauf zurück, eine to_indicators
-Methode zu Array
hinzuzufügen.
def to_indicators
# TODO: Make this less terrible.
Indicator.where id: self.pluck(:id)
end
Manchmal verkettete ich einige dieser Bereiche, um die Ergebnisse innerhalb der Klassenmethoden zu filtern. Obwohl ich eine Methode für ActiveRecord :: Relation aufrufe, weiß ich nicht, wie ich auf dieses Objekt zugreifen kann. Ich kann nur über all
zum Inhalt gelangen. all
ist jedoch ein Array. Also muss ich dieses Array in ein ActiveRecord :: Relation konvertieren. Dies ist zum Beispiel Teil einer der 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, das hat zwei Fragen.
where
zu machen.def self.subjects
-Methode für ein ActiveRecord :: Relation-Objekt auf dieses ActiveRecord :: Relation-Objekt selbst zugreifen?Vielen Dank. Wenn ich etwas klären muss, lassen Sie es mich wissen.
Wie kann ich ein Array von Objekten in ActiveRecord :: Relation konvertieren? Vorzugsweise ohne dabei jedes Mal ein Wo zu tun.
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 den tatsächlichen Daten arbeiten.
Wenn Sie jedoch eine Beziehung wollen, gilt Folgendes:
rufen Sie für ActiveRecord 3.x nicht all
auf, sondern rufen Sie stattdessen scoped
auf. Dies gibt eine Relation zurück, die die gleichen Datensätze darstellt, die all
Ihnen in einem Array geben würde.
für ActiveRecord 4.x rufen Sie einfach all
auf, was eine Relation zurückgibt.
Wie kann ich bei der Ausführung einer
def self.subjects
-Methode für ein ActiveRecord :: Relation-Objekt auf dieses ActiveRecord :: Relation-Objekt selbst zugreifen?
Wenn die Methode für ein Beziehungsobjekt aufgerufen wird, ist self
die Beziehung (im Gegensatz zu der Modellklasse, in der sie definiert ist).
Sie können ein Array von Objekten arr
in eine ActiveRecord :: Relation wie folgt konvertieren (vorausgesetzt, Sie wissen, welche Klasse die Objekte sind und was Sie wahrscheinlich tun).
MyModel.where(id: arr.map(&:id))
Sie müssen jedoch where
verwenden, es ist ein nützliches Werkzeug, das Sie nicht ungern verwenden sollten. Und jetzt haben Sie einen Einzeiler, der ein Array in eine Relation 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 mit IN
generiert, die etwa wie folgt aussieht:
SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....
Denken Sie daran, dass die Reihenfolge des Arrays verloren geht - Da Sie jedoch nur eine Klassenmethode für die collection dieser Objekte ausführen möchten, gehe ich davon aus, dass dies kein Problem darstellt.
In meinem Fall muss ich ein Array von Objekten in ActiveRecord :: Relation konvertieren sowie Sortierung mit einer bestimmten Spalte (id zum Beispiel) konvertieren. Da ich MySQL verwende, könnte die Feldfunktion hilfreich sein.
MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})")
Die SQL sieht so aus:
SELECT ... FROM ... WHERE (id in (11,5,6,7,8,9,10))
ORDER BY field(id,11,5,6,7,8,9,10)
ActiveRecord::Relation
bindet die Datenbankabfrage, die Daten aus der Datenbank abruft.
Angenommen, es macht Sinn, Wir haben ein Array mit Objekten derselben Klasse, und mit welcher Abfrage nehmen wir an, dass wir sie 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, users
return Relation
, bindet jedoch die Datenbankabfrage dahinter und Sie können sie anzeigen
users.to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5) ORDER BY created_at desc"
Daher ist es nicht möglich, ActiveRecord::Relation
aus einem Array von Objekten zurückzugeben, das unabhängig von der SQL-Abfrage ist.
Erstens ist dies KEINE Silberkugel. Aus meiner Erfahrung heraus stellte ich fest, dass das Konvertieren 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.
Das hier Gesagte ist meine Lösung, ich habe die Klasse Array
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::Relation
herausfiltern, um beispielsweise nicht abgeschlossene Elemente herauszunehmen. In diesen Fällen ist es am besten, scope elements.not_finished
zu verwenden und ActiveRecord::Relation
beizubehalten.
Aber manchmal ist diese Bedingung komplexer. Nehmen Sie alle Elemente heraus, die nicht fertig sind und die in den letzten 4 Wochen hergestellt und geprüft wurden. Um das Erstellen neuer Bereiche zu vermeiden, können Sie in ein Array filtern und dann zurückkonvertieren. Denken Sie daran, dass Sie immer noch eine Abfrage an DB durchführen, schnell, da nach id
gesucht wird, aber immer noch eine Abfrage.