【问题标题】:save an active records array保存活动记录数组
【发布时间】:2026-01-24 04:25:02
【问题描述】:

我有一个这样的数组

a = []

a << B.new(:name => "c")
a << B.new(:name => "s")
a << B.new(:name => "e")
a << B.new(:name => "t")

如何一次保存?

【问题讨论】:

    标签: ruby-on-rails ruby


    【解决方案1】:
    B.transaction do
      a.each(&:save!)
    end
    

    这将创建一个循环遍历数组的每个元素并对其调用 element.save 的事务。

    您可以在 Rails 和 Ruby API 中了解 ActiveRecord Transactionsthe each method

    【讨论】:

    • 注意!除非您使用save!,否则这将不起作用。似乎只有在出现异常时才会中止事务。
    • 现在你需要在某个地方赶上ActiveRecord::RecordInvalid
    【解决方案2】:
    a.each(&:save)
    

    这将对数组中的每个项目调用B#save

    【讨论】:

    • 并将其包装在 B.transaction 中,以将其全部保存在一个原子操作中。
    • 永远不要只调用save,要么检查返回值(真或假)或使用.save! 让rails 在出现问题时引发异常!
    【解决方案3】:

    所以我认为我们需要一个中间立场来解决阿列克谢提出例外和中止交易以及乔丹的单线解决方案。我可以提议:

    B.transaction do
      success = a.map(&:save)
      unless success.all?
        errored = a.select{|b| !b.errors.blank?}
        # do something with the errored values
        raise ActiveRecord::Rollback
      end
    end
    

    这将为您提供两全其美:具有回滚的事务,知道哪些记录失败,甚至可以让您访问其中的验证错误。

    【讨论】:

    • 不是!b.errors.blank?,为什么不是b.errors.present?
    • 也可以是a.reject{|b| b.errors.blank?}。主要是因为它几乎是 3 年前的事了,而且这是一个即兴的答案。也是因为 Ruby,我说的对吗?
    【解决方案4】:

    我知道这是一个老问题,但我很惊讶没有人想到这一点:

    B.transaction do
      broken = a.reject { |o| o.save }
      raise ActiveRecord::Rollback if broken.present?
    end
    
    if broken.present?
      # error message
    end
    

    【讨论】:

    • 你应该使用保存!而不是加薪。事务将处理其余部分。
    • @D.Wonnink 是的,但它会使进一步的保存短路,所以你只会知道第一个失败的,而不是所有的。例如,当您有 30 个对象连续保存,其中一半失败并且您必须以一种不会让他重新发送表单 15 次以更正 15 个错误的方式向用户显示验证时,这可能会产生很大的不同一个又一个只是为了学习,又一个东西被打破了;)
    【解决方案5】:

    在事务中包装save 是不够的:如果验证未通过,则不会引发异常并且不会触发回滚。

    我可以建议:

    B.transaction do
      a.each do |o|
        raise ActiveRecord::Rollback unless o.save
      end
    end
    

    仅仅执行B.transaction do a.each(&amp;:save!) end 也不是一种选择,因为事务块不会拯救除ActiveRecord::Rollback 之外的任何异常,并且应用程序会在验证失败时崩溃。

    我不知道以后如何检查记录是否已保存。


    更新。由于有人低估了我的回答,我假设该人正在寻找剪切和粘贴解决方案:),所以这里有一些(丑陋的:))处理失败/成功值的方法:

    save_failed = nil
    B.transaction do
      a.each do |o|
        unless o.save
          save_failed = true
          raise ActiveRecord::Rollback
        end
      end
    end
    if save_failed
      # ...
    else
      # ...
    end
    

    【讨论】:

    • save! 是完美的,transaction do 块将自动恢复所有其他更改。
    • 如果验证失败,您需要手动捕获事务之外的异常。
    • 我想说的是:你有两个选择 a) 在 save 之后使用返回值处理验证 b) 使用 save! 让进程/作业崩溃,有(几乎在所有情况下)没有中间立场。一个单独的save(不处理它的返回值)是一个很大的警告信号。该事务只是确保其他所有内容也得到恢复。
    • 换一种说法:你的例子几乎总是不是你想要的。如果您在a 中的任何对象都无法保存,则整组对象都不会被保存。在事务块之后你不知道:a)如果对象已经保存,b)如果没有,是哪一个导致了回滚 c)问题是什么。我无法想象我想要这种行为的情况。
    • 如果保存失败则恢复所有保存是使用事务的目的。如何处理失败/成功是一个很好的问题,我没有看到一个很好的答案,因为transaction 方法似乎没有将此作为值返回。在我看来,它就像一个 ActiveRecord 缺陷。
    【解决方案6】:

    如果您正在寻找比在循环中保存每一行更有效的解决方案,请在此处查看我的答案Ruby on Rails - Import Data from a CSV file

    我建议在那里使用 gem activerecord-import

    【讨论】: