115 votes

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

J'ai un tableau d'objets, appelons-le "tableau". Indicator . Je veux exécuter les méthodes de la classe Indicator (celles de la classe def self.subjects variété, scopes, 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 faire en sorte qu'ils soient des ActiveRecord::Relation. Je finis donc par ajouter un to_indicators pour Array .

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

Parfois, j'enchaîne plusieurs de ces scopes pour filtrer les résultats, dans les méthodes de la classe. Ainsi, même si j'appelle une méthode sur un ActiveRecord::Relation, je ne sais pas comment accéder à cet objet. Je ne peux accéder à son contenu que par le biais de all . Mais all est un tableau. Je dois donc convertir ce tableau en ActiveRecord::Relation. Par exemple, ceci 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 Array d'objets en ActiveRecord::Relation ? De préférence sans faire un where à chaque fois.
  2. Lors de l'exécution d'un def self.subjects sur un objet ActiveRecord::Relation, comment puis-je accéder à cet objet ActiveRecord::Relation lui-même ?

Merci. Si je dois clarifier quelque chose, faites-le moi savoir.

182voto

Marco Prins Points 662

Vous pouvez convertir un tableau d'objets arr à un ActiveRecord::Relation comme ceci (en supposant que vous connaissez la classe des objets, ce qui est probablement le cas)

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

Vous devez utiliser where Mais c'est un outil utile que vous ne devez pas hésiter à utiliser. Et maintenant vous avez un one-liner pour convertir un tableau en relation.

map(&:id) transformera votre tableau d'objets en un tableau contenant uniquement leurs identifiants. Et le fait de passer un tableau à une clause where générera une instruction SQL avec les éléments suivants 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 n'est que d'exécuter une méthode de classe sur l'ordinateur collection de ces objets, je suppose que ce ne sera pas un problème.

48voto

Andrew Marshall Points 43955

Comment convertir un Array d'objets en ActiveRecord::Relation ? De préférence sans faire un where à chaque fois.

Vous ne pouvez pas convertir un Array en une ActiveRecord::Relation car une Relation est juste un constructeur pour une requête SQL et ses méthodes n'opèrent pas sur des données réelles.

Cependant, si ce que vous voulez est une relation, alors.. :

  • pour ActiveRecord 3.x, ne pas appeler all et appeler à la place scoped qui renverra une Relation qui représente les mêmes enregistrements que ceux que l'on trouve sur le site. all vous donnerait dans un Array.

  • pour ActiveRecord 4.x, il suffit d'appeler all qui renvoie une relation.

Lors de l'exécution d'un def self.subjects sur un objet 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).

8voto

Xingcheng Xia Points 89

Eh bien, dans mon cas, j'ai besoin conversion d'un tableau d'objets en ActiveRecord::Relation ainsi que trier avec une colonne spécifique (id par exemple). Comme j'utilise MySQL, le fonction du champ pourrait être utile.

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

Le SQL ressemble à ça :

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

2voto

Haris Krajina Points 3051

Tout d'abord, il ne s'agit PAS d'une solution miracle. D'après mon expérience, j'ai constaté que la conversion à la relation est parfois plus facile que les autres solutions. J'essaie d'utiliser cette approche avec beaucoup de parcimonie et uniquement dans les cas où l'alternative serait plus complexe.

Cela étant dit, voici ma solution, j'ai étendu Array classe

# 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, retirez les éléments non complétés. Dans ces cas, le mieux est d'utiliser la portée elements.not_finished et vous garderiez 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 nouveaux scopes, vous pouvez filtrer vers un tableau et ensuite reconvertir. Gardez à l'esprit que vous effectuez toujours une requête à la base de données, rapide puisqu'elle effectue une recherche par id mais toujours une requête.

1voto

ray Points 5072

ActiveRecord::Relation lie la requête de la base de données qui récupère les données de la base.

Supposons, pour donner du sens, que nous ayons un tableau avec des objets de la même classe, alors avec quelle requête nous devons 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 en haut, users retourner Relation mais lie la requête de la base de données derrière elle et vous pouvez la visualiser,

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 retourner ActiveRecord::Relation à partir d'un tableau d'objets qui est indépendant de la requête sql.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X