在 Rail 中的单个调用中保存多个对象

我在 Rail 中有一个方法,它是这样做的:

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)

只需要两个数据库就能完成。有没有一个简单的方法可以在轨道上做到这一点,还是我只能一次做一个?

119617 次浏览

You might try using Foo.create instead of Foo.new. Create "Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not."

You can create multiple objects like this:

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

Then, for each parent, you can also use create to add to its association:

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

I recommend reading both the ActiveRecord documentation and the Rails Guides on ActiveRecord query interface and ActiveRecord associations. The latter contains a guide of all the methods a class gains when you declare an association.

Since you need to perform multiple inserts, database will be hit multiple times. The delay in your case is because each save is done in different DB transactions. You can reduce the latency by enclosing all your operations in one transaction.

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

Your save method might look like this:

# 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

The save call on the parent object saves the child objects.

One of the two answers found somewhere else: by Beerlington. Those two are your best bet for performance


I think your best bet performance-wise is going to be to use SQL, and bulk insert multiple rows per query. If you can build an INSERT statement that does something like:

INSERT INTO foos_bars (foo_id,bar_id) VALUES (1,1),(1,2),(1,3).... You should be able to insert thousands of rows in a single query. I didn't try your mass_habtm method, but it seems like you could to something like:


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

Also, if you are searching Bar by "some_attribute", make sure you have that field indexed in your database.


OR

You still might have a look at activerecord-import. It's right that it doesn't work without a model, but you could create a Model just for the import.


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

Cheers

You don't need a gem to hit DB fast and only once!

Jackrg has worked it out for us: https://gist.github.com/jackrg/76ade1724bd816292e4e

you need to use this gem "FastInserter" -> https://github.com/joinhandshake/fast_inserter

and inserting a large number and thousands of records is fast because this gem skips active record, and only uses a single sql raw query

insert_all (Rails 6+)

Rails 6 introduced a new method insert_all, which inserts multiple records into the database in a single SQL INSERT statement.

Also, this method does not instantiate any models and does not call Active Record callbacks or validations.

So,

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

it is significantly more efficient than

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

if all you want to do is to insert new records.