web-dev-qa-db-fra.com

Enregistrement de plusieurs objets en un seul appel dans des rails

J'ai une méthode dans Rails qui fait quelque chose comme ceci:

a = Foo.new("bar")
a.save

b = Foo.new("baz")
b.save

...
x = Foo.new("123", :parent_id => a.id)
x.save

...
z = Foo.new("zxy", :parent_id => b.id)
z.save

Le problème est que cela prend de plus en plus de temps en temps, plus j'ajoute d'entités. Je suppose que cela est dû au fait qu’il doit accéder à la base de données pour chaque enregistrement. Comme ils sont imbriqués, je sais que je ne peux pas sauver les enfants avant que les parents ne soient sauvés, mais je voudrais sauver tous les parents en même temps, puis tous les enfants. Ce serait bien de faire quelque chose comme:

a = Foo.new("bar")
b = Foo.new("baz")
...
saveall(a,b,...)

x = Foo.new("123", :parent_id => a.id)
...
z = Foo.new("zxy", :parent_id => b.id)
saveall(x,...,z)

Cela ferait tout en seulement deux accès à la base de données. Y a-t-il un moyen facile de faire cela dans Rails, ou suis-je obligé de le faire un à la fois?

73
captncraig

Vous pouvez essayer d'utiliser Foo.create au lieu de Foo.new. Créer "Crée un objet (ou plusieurs objets) et l'enregistre dans la base de données si les validations sont réussies. L'objet résultant est renvoyé, que l'objet ait été enregistré avec succès dans la base de données ou non."

Vous pouvez créer plusieurs objets comme ceci:

# Create an Array of new objects
  parents = Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])

Ensuite, pour chaque parent, vous pouvez également utiliser create pour ajouter à son association:

parents.each do |parent|
  parent.children.create (:child_name => 'abc')
end

Je recommande de lire à la fois la documentation ActiveRecord et les guides Rails sur Interface de requête ActiveRecord et Associations ActiveRecord . Ce dernier contient un guide de toutes les méthodes qu'une classe gagne lorsque vous déclarez une association.

60
Roadmaster

Puisque vous devez effectuer plusieurs insertions, la base de données sera touchée plusieurs fois. Le retard dans votre cas est dû au fait que chaque sauvegarde est effectuée dans différentes transactions de base de données. Vous pouvez réduire le temps de latence en regroupant toutes vos opérations dans une transaction.

class Foo
  belongs_to  :parent,   :class_name => "Foo"
  has_many    :children, :class_name => "Foo", :foreign_key=> "parent_id"
end

Votre méthode de sauvegarde pourrait ressembler à ceci:

# build the parent and the children
a = Foo.new(:name => "bar")
a.children.build(:name => "123")

b = Foo.new("baz")
b.children.build(:name => "zxy")

#save parents and their children in one transaction
Foo.transaction do
  a.save!
  b.save!
end

L'appel save sur l'objet parent enregistre les objets enfants.

77
Harish Shetty

Une des deux réponses trouvées ailleurs: by Beerlington . Ces deux solutions sont votre meilleur pari pour la performance.


Je pense que votre meilleur choix en termes de performances sera d’utiliser SQL et d’insérer plusieurs lignes en bloc par requête. Si vous pouvez créer une instruction INSERT faisant quelque chose comme:

INSERT INTO foos_bars (foo_id, bar_id) VALEURS (1,1), (1,2), (1,3) .... Vous devriez pouvoir insérer des milliers de lignes dans une même requête. Je n'ai pas essayé votre méthode mass_habtm, mais il semble que vous puissiez utiliser quelque chose comme:


bars = Bar.find_all_by_some_attribute(:a) 
foo = Foo.create
values = bars.map {|bar| "(#{foo.id},#{bar.id})"}.join(",") 
connection.execute("INSERT INTO foos_bars (foo_id, bar_id) VALUES
#{values}")

De plus, si vous recherchez Bar par "some_attribute", assurez-vous que ce champ est indexé dans votre base de données.


OR

Vous pouvez toujours jeter un coup d'oeil à activerecord-import. Il est vrai que cela ne fonctionne pas sans modèle, mais vous pouvez créer un modèle uniquement pour l'importation.


FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]]

À votre santé

8
Nguyen Chien Cong

vous devez utiliser cette gemme "FastInserter" -> https://github.com/joinhandshake/fast_inserter

et insérer un grand nombre d'enregistrements et des milliers d'enregistrements est rapide, car cette gem ignore l'enregistrement actif et utilise uniquement une requête sql raw

0
val caro