如何在Ruby on Rails中复制一条记录及其关联记录?

4

在我的Rails应用程序中,我有发票(invoices)及其相关物品(items)。

在我的InvoicesController中,我添加了这个方法:

def duplicate
  invoice = Invoice.find(params[:id])
  @invoice = invoice.dup
  invoice.items.each do |item|
    @invoice.items << item
  end      
  render :new    
end

def create
  @invoice = current_user.invoices.build(params[:invoice])    
  if @invoice.save
    flash[:success] = "Invoice created."
    redirect_to edit_invoice_path(@invoice)
  else
    render :new
  end
end

点击链接会实例化一个带有正确的invoiceitems数据的表单。

但是,在尝试Create记录时,我遇到了一个错误:

Couldn't find Item with ID=4 for Invoice with ID=

有没有人能告诉我我在这里缺少什么,或者是否有更聪明的方法来复制一条记录,包括其关联记录?

谢谢任何帮助。


1
你能发布错误的完整回溯吗? - Stuart M
2个回答

3

这里有一些代码可以复制主对象以及其下面的每个子项。新对象和子项都不会被保存(暂时)。

  def duplicate
    dup.tap do |new_invoice|
      items.each do |item|
        new_invoice.items.push item.dup
      end
    end
  end

以下是一个快速测试来证明这些事情:

require 'test_helper'

class InvoiceTest < ActiveSupport::TestCase

  def original_invoice
    Invoice.create(number: 5).tap do |invoice|
      invoice.items.create(name: "a", price: "5")
      invoice.items.create(name: "b", price: "50")
    end
  end
  test "duplication" do
    new_invoice = original_invoice.duplicate
    new_invoice.save
    assert_equal 2, new_invoice.items.count
  end
end

嘿,Jesse。谢谢!对于我这个新手来说,你的代码看起来非常整洁和优雅。我刚刚了解了“tap”方法,但我仍然不明白如何将发票的ID复制到“duplicate”方法中。(我们肯定需要它在那里,对吧?)大概是这样的:invoice = Invoice.find(params[:id])... 我想是“tap”方法让我感到困惑... - Tintin81
1
“duplicate”将在发票模型中使用... 因此,您可以像这样使用它:@new_invoice = Invoice.find(params[:id]).duplicate - Jesse Wolgamott
1
Tap操作将会1) 返回对象,2) 在返回对象之前对其进行操作。 - Jesse Wolgamott
现在我明白了...非常好用!非常感谢你的帮助。 - Tintin81
这实际上是我一直在寻找的完美的“瘦控制器/胖模型”解决方案。 - Tintin81

2

让我们逐步解构一下,从错误信息开始。

无法找到ID为4的项目,以用于ID为空的发票

初看起来,可能会想到没有ID为4的项目。确保像这样的简单事情不是问题是一个好的合理性检查。在这种情况下,我们已经找到了正确的项目,所以可以继续。

一开始让我感到奇怪的是在ID=后面缺少一个数字。原来这提示了问题所在。

让我们来看一下一些控制台输出。我将使用猫对象,仅仅因为它们很棒。

我们想做的第一件事是获取一个猫:

Cat.first
=> Cat Load (0.2ms)  SELECT "cats".* FROM "cats" LIMIT 1
=> #<Cat id: 2, age: 6, birthdate: "2013-06-08", color: "brown", name: "Aaron", gender: "m", created_at: "2013-06-08 21:44:22", updated_at: "2013-06-08 21:44:22", user_id: 1> 

在我们拥有这只猫之后,让我们将其复制。

Cat.first.dup
Cat Load (0.3ms)  SELECT "cats".* FROM "cats" LIMIT 1
=> #<Cat age: 6, birthdate: "2013-06-08", color: "brown", name: "Aaron", gender: "m", created_at: nil, updated_at: nil, user_id: 1> 

我们注意到这个重复的猫有什么特点呢?首先,created_atupdated_at都是nil。这通常意味着对象尚未保存到数据库中。如果我们去查找CatID,我们会发现甚至没有列来存储它!让我们尝试保存这个新对象。
Cat.first.dup.save
Cat Load (0.3ms)  SELECT "cats".* FROM "cats" ORDER BY "cats"."id" DESC LIMIT 1
(0.1ms)  begin transaction
SQL (0.7ms)  INSERT INTO "cats" ("age", "birthdate", "color", "created_at", "gender", "name", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?, ?, ?)  [["age", 6], ["birthdate", Sat, 08 Jun 2013], ["color", "brown"], ["created_at", Sun, 09 Jun 2013 18:10:47 UTC +00:00], ["gender", "m"], ["name", "Aaron"], ["updated_at", Sun, 09 Jun 2013 18:10:47 UTC +00:00], ["user_id", 1]]
(0.7ms)  commit transaction
=> true 

现在,如果我们调用Cat.last,它将返回此对象。
Cat.last
Cat Load (0.3ms)  SELECT "cats".* FROM "cats" ORDER BY "cats"."id" DESC LIMIT 1
=> #<Cat id: 11, age: 6, birthdate: "2013-06-08", color: "brown", name: "Aaron", gender: "m", created_at: "2013-06-09 18:10:47", updated_at: "2013-06-09 18:10:47", user_id: 1> 

你的问题在于,虽然你复制了 Invoice,但没有将其保存到数据库中。Couldn't find Item with ID=4 for Invoice with ID= 证实了这一点,因为它告诉我们复制的 Invoice 没有 ID,这只有在它没有被保存时才会出现。
祝好!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接