web-dev-qa-db-fra.com

Conversion d'un tableau d'objets en ActiveRecord :: Relation

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.

  1. Comment convertir un tableau d'objets en ActiveRecord :: Relation? De préférence sans faire where à chaque fois.
  2. Lors de l'exécution d'une méthode de type 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.

88
Nathan

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

42
Andrew Marshall

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.

140
Marco Prins

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)

Fonction de champ MySQL

4
Xingcheng Xia

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, usersreturn 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.

0
ray

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.

0
Haris Krajina