Reputation: 10207
In my Rails app I have invoices
and their associated items
.
In my InvoicesController
I added this method:
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
Clicking the link instantiates a form with the correct invoice
and items
data.
However, when trying to Create
the record I get an error:
Couldn't find Item with ID=4 for Invoice with ID=
Can anybody tell me what I am missing here or if there's a smarter way to duplicate a record including its associated records?
Thanks for any help.
Upvotes: 4
Views: 1003
Reputation: 40277
Here's cose that will duplicate the main object, and each of the items underneath it. The new object will not be saved, nor will the items (yet).
def duplicate
dup.tap do |new_invoice|
items.each do |item|
new_invoice.items.push item.dup
end
end
end
And a quick test to prove the things:
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
Upvotes: 3
Reputation: 1755
Let's deconstruct this a little bit, starting with your error message.
Couldn't find Item with ID=4 for Invoice with ID=
Now, at first thought, one might be tempted to consider that there is no Item with ID 4. It's a good sanity check to make sure that simple things like this aren't the issue. In this case, we have the appropriate item, so we can move on.
What struck me as odd at first was the lack of a number following ID=
. It turns out that this hints as to what the problem is.
Let's have a look at some console output. I will be using Cat objects, simply because they are awesome.
The first thing we want to do is to get a Cat:
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>
After we have the cat, let's duplicate it.
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>
What do we notice about the duplicated cat? Well, for starters, both created_at
and updated_at
are nil
. This is usually a sign that the object hasn't be saved to the database. If we go to look for the Cat
's ID
, we notice that there isn't even a column for that!
Let's try saving the new object.
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
Now, if we call Cat.last
, it will return this object.
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>
Your problem is that, while you are duplicating the Invoice
, you aren't saving it to the database. Couldn't find Item with ID=4 for Invoice with ID=
confirms this as it tells us the duplicated Invoice
has no ID
, which would only be the case if it had not been saved.
Cheers!
Upvotes: 2