J'ai un tableau d'objets, appelons-le une Indicator
. Je veux exécuter des méthodes de la classe Indicator (celles de la variété def self.subjects
, des étendues, etc.) sur ce tableau. Le seul moyen que je connaisse pour exécuter des méthodes de classe sur un groupe d'objets est de les avoir comme ActiveRecord :: Relation. Je finis donc par avoir recours à l’ajout d’une méthode to_indicators
à Array
.
def to_indicators
# TODO: Make this less terrible.
Indicator.where id: self.pluck(:id)
end
Parfois, j'enchaîne plusieurs de ces portées pour filtrer les résultats, au sein des méthodes de classe. Ainsi, même si j'appelle une méthode sur une relation ActiveRecord ::, je ne sais pas comment accéder à cet objet. Je ne peux en connaître le contenu que par all
. Mais all
est un tableau. Alors je dois convertir ce tableau en ActiveRecord :: Relation. Par exemple, cela fait partie d'une des méthodes:
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
Je suppose que cela se résume à deux questions.
where
à chaque fois.def self.subjects
sur un ActiveRecord :: Relation, comment puis-je accéder à cet objet ActiveRecord :: Relation lui-même?Merci. Si j'ai besoin de clarifier quelque chose, faites le moi savoir.
Comment convertir un tableau d'objets en ActiveRecord :: Relation? De préférence sans faire où à chaque fois.
Vous ne pouvez pas convertir un tableau en ActiveRecord :: Relation car une relation n'est qu'un générateur pour une requête SQL et que ses méthodes ne fonctionnent pas sur des données réelles.
Cependant, si vous voulez une relation, alors:
pour ActiveRecord 3.x, n’appelez pas all
mais appelez scoped
, qui rendra une relation qui représente les mêmes enregistrements que all
vous donnerait dans un tableau.
pour ActiveRecord 4.x, appelez simplement all
, qui renvoie une relation.
Lorsque j'exécute une méthode de type
def self.subjects
sur un ActiveRecord :: Relation, comment puis-je accéder à cet objet ActiveRecord :: Relation lui-même?
Lorsque la méthode est appelée sur un objet Relation, self
est la relation (par opposition à la classe de modèle dans laquelle elle est définie).
Vous pouvez convertir un tableau d'objets arr
en une relation ActiveRecord :: comme ceci (en supposant que vous sachiez de quelle classe sont les objets, ce que vous faites probablement)
MyModel.where(id: arr.map(&:id))
Cependant, vous devez utiliser where
, c'est un outil utile qu'il ne faut pas hésiter à utiliser. Et maintenant, vous avez un one-liner qui convertit un tableau en relation.
map(&:id)
transformera votre tableau d'objets en un tableau contenant uniquement leurs identifiants. Et passer un tableau à une clause where va générer une instruction SQL avec IN
qui ressemble à quelque chose comme:
SELECT .... WHERE `my_models`.id IN (2, 3, 4, 6, ....
Gardez à l'esprit que l'ordre du tableau sera perdu - Mais puisque votre objectif est uniquement d'exécuter une méthode de classe sur la collection de ces objets, je suppose que ce ne sera pas un problème.
Eh bien, dans mon cas, j'ai besoin de convertir un tableau d'objets en ActiveRecord :: Relation ainsi que trier avec une colonne spécifique (id par exemple). Depuis que j'utilise MySQL, la fonction field pourrait être utile.
MyModel.where('id in (?)',ids).order("field(id,#{ids.join(",")})")
Le SQL ressemble à:
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
lie la requête de base de données qui récupère les données de la base de données.
Supposons que cela ait un sens, nous avons un tableau avec des objets de la même classe, puis avec quelle requête nous supposons les lier?
Quand je cours,
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
Ici ci-dessus, users
return Relation
object mais lie la requête de base de données derrière lui et vous pouvez le voir,
users.to_sql
=> "SELECT `users`.* FROM `users` WHERE `users`.`id` IN (1, 3, 4, 5) ORDER BY created_at desc"
Il n'est donc pas possible de renvoyer ActiveRecord::Relation
à partir d'un tableau d'objets indépendant de la requête SQL.
Tout d'abord, ce n'est pas une solution miracle. De mon expérience, j'ai trouvé que la conversion en relation est parfois plus facile que les alternatives. J'essaie d'utiliser cette approche avec parcimonie et uniquement dans les cas où l'alternative serait plus complexe.
Cela étant dit voici ma solution, j'ai étendu la classe Array
# 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
Un exemple d'utilisation serait array.to_activerecord_relation.update_all(status: 'finished')
. Maintenant, où est-ce que je l'utilise?
Parfois, vous devez filtrer ActiveRecord::Relation
, par exemple supprimer des éléments non terminés. Dans ces cas, le mieux est d'utiliser scope elements.not_finished
et de conserver toujours ActiveRecord::Relation
.
Mais parfois, cette condition est plus complexe. Retirez tous les éléments qui ne sont pas finis et qui ont été produits au cours des 4 dernières semaines et ont été inspectés. Pour éviter de créer de nouvelles étendues, vous pouvez filtrer sur un tableau puis reconvertir. N'oubliez pas que vous continuez à effectuer une requête sur la base de données, rapidement, car celle-ci effectue une recherche avec id
mais demeure une requête.