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

cap*_*aig 78 activerecord data-access ruby-on-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
Run Code Online (Sandbox Code Playgroud)

问题是我添加的实体越多越长.我怀疑这是因为它必须为每条记录命中数据库.由于它们是嵌套的,我知道在父母得救之前我无法拯救孩子,但我想立刻拯救所有的父母,然后是所有的孩子.做一些像这样的事情会很好:

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)
Run Code Online (Sandbox Code Playgroud)

这只会在两次数据库命中时完成.有没有一种简单的方法可以在rails中执行此操作,或者我一次只执行一次?

Har*_*tty 90

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

class Foo
  belongs_to  :parent,   :class_name => "Foo"
  has_many    :children, :class_name => "Foo", :foreign_key=> "parent_id"
end
Run Code Online (Sandbox Code Playgroud)

您的保存方法可能如下所示:

# 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
Run Code Online (Sandbox Code Playgroud)

save对父对象的调用保存子对象.

  • 正是我在寻找的东西.加速了我的种子.谢谢 :-) (3认同)

Roa*_*ter 62

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

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

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

然后,对于每个父级,您还可以使用create添加到其关联:

parents.each do |parent|
  parent.children.create (:child_name => 'abc')
end
Run Code Online (Sandbox Code Playgroud)

我建议在ActiveRecord查询接口ActiveRecord关联上阅读ActiveRecord文档和Rails指南.后者包含了声明关联时类获得的所有方法的指南.

  • 不幸的是,ActiveRecord将为每个创建的模型生成一个INSERT查询.OP想要一个INSERT调用,ActiveRecord不会这样做. (70认同)
  • 确实,你无法获得AR来生成一个INSERT或UPDATE,但是使用`ActiveRecord :: Base.transaction {records.each(&:save)}`或者类似的,你至少可以将所有INSERT或UPDATE放入一个交易. (3认同)

Mar*_*n13 22

insert_all (Rails 6+)

Rails 6引入了一个新方法insert_all,它在单个SQL INSERT语句中将多条记录插入到数据库中。

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

所以,

Foo.insert_all([
  { first_name: 'Jamie' },
  { first_name: 'Jeremy' }
])
Run Code Online (Sandbox Code Playgroud)

它的效率明显高于

Foo.create([
  { first_name: 'Jamie' },
  { first_name: 'Jeremy' }
])
Run Code Online (Sandbox Code Playgroud)

如果您只想插入新记录。

  • @JinLim 不是啥!做。 (4认同)
  • 需要注意的一件事是:insert_all 跳过 AR 回调和验证:https://edgeguides.rubyonrails.org/active_record_callbacks.html#skipping-callbacks (3认同)
  • 我等不及更新我们的应用程序了。Rails 6 中有很多很酷的东西。 (2认同)
  • 如果您想验证实体,请选中“insert_all!”。 (2认同)

Ngu*_*ong 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}")
Run Code Online (Sandbox Code Playgroud)

此外,如果您通过"some_attribute"搜索Bar,请确保在数据库中将该字段编入索引.


要么

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


FooBar.import [:foo_id, :bar_id], [[1,2], [1,3]]
Run Code Online (Sandbox Code Playgroud)

干杯

  • 要进行更新,您应该使用upsert:https://github.com/seamusabshere/upsert.干杯 (2认同)