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

时间:2010-03-24 16:11:20

标签: ruby-on-rails activerecord data-access

我在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)

只需两次数据库命中即可完成所有操作。有没有一种简单的方法可以在rails中执行此操作,或者我一次只能做一次?

6 个答案:

答案 0 :(得分:82)

由于您需要执行多次插入,因此数据库将被多次命中。您的情况延迟是因为每次保存都是在不同的数据库事务中完成的。您可以通过将所有操作包含在一个事务中来减少延迟。

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调用会保存子对象。

答案 1 :(得分:61)

您可以尝试使用Foo.create而不是Foo.new。如果验证通过,则创建“创建一个对象(或多个对象)并将其保存到数据库。无论对象是否已成功保存到数据库,都会返回结果对象。”

您可以创建多个这样的对象:

# 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 documentationActiveRecord query interface上阅读ActiveRecord associations和Rails指南。后者包含了当您声明关联时类获得的所有方法的指南。

答案 2 :(得分:16)

insert_all(超过6条)

Rails 6引入了一种新方法insert_all,该方法通过一条SQL INSERT语句将多个记录插入数据库。

此外,此方法不会实例化任何模型,并且不会调用Active Record回调或验证。

所以

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

它的效率明显高于

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

如果您要做的就是插入新记录。

答案 3 :(得分:9)

在其他地方找到了两个答案中的一个:Beerlington。 这两个是你表现最好的选择


我认为在性能方面最好的选择是使用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,请确保在数据库中将该字段编入索引。


或者

您仍然可以查看activerecord-import。没有模型它是行不通的,但你可以创建一个仅用于导入的模型。


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

干杯

答案 4 :(得分:0)

你不需要宝石快速击中DB而只需要一次!

Jackrg为我们解决了这个问题: https://gist.github.com/jackrg/76ade1724bd816292e4e

答案 5 :(得分:0)

您需要使用此宝石“ FastInserter”-> https://github.com/joinhandshake/fast_inserter

并插入大量和数千条记录很快,因为该gem跳过了活动记录,并且仅使用单个sql原始查询