web-dev-qa-db-ja.com

railsの単一の呼び出しで複数のオブジェクトを保存する

Railsに以下のようなことをしているメソッドがあります:

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

問題は、追加するエンティティが増えるほど、時間がかかることです。これは、レコードごとにデータベースにアクセスする必要があるためだと思われます。ネストされているため、親が保存される前に子を保存できないことはわかっていますが、すべての親を一度に保存してから、すべての子を保存したいと思います。次のようなことをするといいでしょう。

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)

それは、たった2つのデータベースヒットですべてを実行します。 Railsでこれを簡単に行う方法はありますか?

77
captncraig

Foo.newの代わりにFoo.createを使用してみてください。作成「検証に合格した場合、オブジェクト(または複数のオブジェクト)を作成し、データベースに保存します。オブジェクトがデータベースに正常に保存されたかどうかにかかわらず、結果のオブジェクトが返されます。」

次のような複数のオブジェクトを作成できます。

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

次に、親ごとにcreateを使用して、関連付けに追加することもできます。

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

ActiveRecord documentation とRails ActiveRecord query interface および ActiveRecord associations のガイドの両方を読むことをお勧めします。後者には、関連付けを宣言するときにクラスが取得するすべてのメソッドのガイドが含まれています。

62
Roadmaster

複数の挿入を実行する必要があるため、データベースは複数回ヒットします。この場合の遅延は、各保存が異なるDBトランザクションで行われるためです。すべての操作を1つのトランザクションで囲むことにより、待ち時間を短縮できます。

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

保存方法は次のようになります。

# 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

親オブジェクトのsave呼び出しは、子オブジェクトを保存します。

86
Harish Shetty

別の場所で見つかった2つの答えの1つ: Beerlington によって。これら2つがパフォーマンスの最善策です


パフォーマンスに関しては、SQLを使用し、クエリごとに複数行を一括挿入するのが最善策だと思います。次のようなことを行うINSERTステートメントを作成できる場合:

INSERT INTO foos_bars(foo_id、bar_id)VALUES(1,1)、(1,2)、(1,3)....単一のクエリで数千行を挿入できるはずです。 mass_habtmメソッドは試しませんでしたが、次のようなことができるようです。


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}")

また、「some_attribute」でBarを検索している場合は、データベースでそのフィールドがインデックス化されていることを確認してください。


OR

あなたはまだactiverecord-importを見ているかもしれません。モデルなしでは機能しないことは正しいですが、インポートのためだけにモデルを作成することもできます。


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

乾杯

9

DBを高速で1回だけヒットするためにgemは必要ありません!

Jackrgは私たちのためにそれを解決しました: https://Gist.github.com/jackrg/76ade1724bd816292e4e

0

このgem "FastInserter"を使用する必要があります-> https://github.com/joinhandshake/fast_inserter

このgemはアクティブなレコードをスキップし、単一のsql rawクエリのみを使用するため、多数および数千のレコードを挿入するのは高速です

0
val caro