Railsの1回の呼び出しで複数のオブジェクトを保存する


92

私はレールの中で次のようなことをしているメソッドを持っています:

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つのデータベースヒットでそれをすべて行います。これをレールで行う簡単な方法はありますか、それとも一度に1つずつ行うのが難しいのですか?

回答:


65

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のドキュメントと、ActiveRecordクエリインターフェイスActiveRecordの関連付けに関するRailsガイドの両方を読むことをお勧めします。後者には、関連付けを宣言したときにクラスが取得するすべてのメソッドのガイドが含まれています。


77
残念ながら、ActiveRecordは作成されたモデルごとに1つのINSERTクエリを生成します。OPは単一のINSERT呼び出しを必要としていますが、ActiveRecordはこれを行いません。
フランソワボーソレイユ

はい、すべてを1回の挿入呼び出しで取得することを望んでいましたが、activerecordがそれほど賢くない場合、それは簡単ではないと思います。
captncraig 2010

@FrançoisBeausoleilは、stackoverflow.com / questions / 15386450 / …という質問を見ていただけませんか。これが、同時に複数のレコードを挿入できない理由でしょうか。
Richlewis 2013年

3
ARで1つのINSERTまたはUPDATEを生成できないのは事実ですがActiveRecord::Base.transaction { records.each(&:save) }、同様の方法で、少なくともすべてのINSERTまたはUPDATEを単一のトランザクションに入れることができます。
yuval

1
実際、OPはデータベースへのアクセスを減らし、DBアクセスを高速化したいと考えています。ActiveRecordは実際に、1つのトランザクションですべての呼び出しをバッチ処理することでそれを実現します。(受け入れられる答えになるはずのHarishの答えを参照してください。)ActiveRecordで実行できないのは、DBにトランザクションごとに1つのINSERTクエリを作成させることですが、待ち時間はネットワークの実行に起因するため、それほど重要ではありません。 INSERTクエリを実行するとき、DB自体の内部ではなく、DBへのアクセス。
Magne、

98

複数の挿入を実行する必要があるため、データベースは複数回ヒットします。あなたの場合の遅れは、各保存が異なる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親オブジェクトの呼び出しは、子オブジェクトを保存します。


3
まさに私が探していたもの。私の種をたくさんスピードアップします。ありがとう:-)
Renra

12

insert_all(Rails 6以降)

Rails 6データベースに複数のレコードを1回で挿入する新しいメソッドinsert_allが導入されましたSQL INSERTステートメントで。

また、このメソッドはモデルをインスタンス化せず、Active Recordコールバックや検証を呼び出しません。

そう、

Foo.insert_all([
  { first_name: 'Jamie' },
  { first_name: 'Jeremy' }
])

よりもはるかに効率的です

Foo.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])

新しいレコードを挿入するだけの場合。


1
アプリが更新されるまで待ちきれません。Railsの6のように多くのクールなもの
ダン

注意すべき点の1つは、insert_allがARコールバックと検証をスキップすることです。edgeguides.rubyonrails.org
sujay

10

Beerlingtonによる別の場所で見つかった2つの回答の1つ。これら2つは、パフォーマンスのための最善の策です


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

INSERT INTO foos_bars(foo_id、bar_id)VALUES(1,1)、(1,2)、(1,3).... 1つのクエリで数千の行を挿入できるはずです。私はあなたの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を検索している場合は、データベースでそのフィールドにインデックスが付けられていることを確認してください。


または

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


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

乾杯


これは挿入には最適ですが、1つのトランザクションで複数のレコードを更新する場合はどうでしょうか。
アビシャイ2013

2
更新するには、upsertを使用する必要があります:github.com/seamusabshere/upsert。歓声
グエンチエンコン

SQLクエリに関する非常に悪い考え。ActiveRecordとトランザクションを使用する必要があります。
ケロズ2014年

それは悪い考えではありません。1つの挿入を実行している場合、それは成功するか失敗するかのどちらかであり、トランザクションの必要はないと思います。または、常にその1つの挿入をトランザクションブロックでラップできます。
フェルナンドファブレティ2014

これは悪いレールの慣習です
ブレアアンダーソン

1

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

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


1
gemへのリンクは役立つかもしれませんが、Askerが現在のコードの代わりに使用できるコードを提供してください(質問を参照)。
トリンコット


1
回答には、重要な情報を埋め込む必要があります。回答を編集してリンクをそこに追加し、必要な部分を回答内に追加して、自己完結型にしてください。
トリンコット

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.