7

The error "Couldn't find Item with ID=123 for Payment with ID=" occurs when adding existing Item models to a new Payment model. This is in a has_many relationship and using accepts_nested_attributes_for.

class Payment < ActiveRecord::Base
  has_many :items
  accepts_nested_attributes_for :items
  ...

class Item < ActiveRecord::Base
  belongs_to :payment
  ...

The payment and item models are hypothetical but the problem is real. I need to associate the Items with the Payment (as I do in the new action) before saving the Payment (done in the create action). The user needs to modify attributes on the Items as they create the Payment (adding a billing code would be an example).

Specifically, the error occurs in the controller's create action:

# payments_controller.rb

def new
  @payment = Payment.new
  @payment.items = Item.available # where(payment_id: nil)
end

def create
  @payment = Payment.new payment_params # <-- error happens here
  ...
end

def payment_params
  params.require(:payment).permit( ..., [items_attributes: [:id, :billcode]])
end

lib/active_record/nested_attributes.rb:543:in 'raise_nested_attributes_record_not_found!' is immediately prior to it in the callstack. It is curious that ActiveRecord is including payment_id as part of the search criteria.

For the sake of being complete, the form looks something like this...

form_for @payment do |f|
   # payment fields ...
   fields_for :items do |i|
     # item fields    

and it renders the Items correctly on the new action. The params passed through the form look like this:

{ "utf8"=>"✓",
  "authenticity_token"=>"...",
  "payment"=>{
    "items_attributes"=>{
      "0"=>{"billcode"=>"123", "id"=>"192"}
    },
  }
}

If there is a better way to approach this that doesn't use accepts_nested_attributes_for I'm open to suggestions.

IAmNaN
  • 10,305
  • 3
  • 53
  • 51
  • 1
    Hey, I think you should modify your payment_params to allow :id in item_attributes like this: `params.require(:payment).permit( ..., [items_attributes: [:id, :billcode]])`. This way, the right available item can be extracted from the database. Let me know if this works. ` – cristian Jun 06 '14 at 20:52
  • Oh, you're right. It's in my code just not this question. You can see it in the params hash that is getting passed back. I'll fix the code snippet. Appreciate you catching that. – IAmNaN Jun 06 '14 at 23:14
  • Is your `payment_id` in your `items` is saving properly? – Pavan Jun 07 '14 at 07:27
  • It never gets that far. The Payment model is still a new record at the point when the error occurs. – IAmNaN Jun 09 '14 at 21:22
  • Looks similar to [this](https://stackoverflow.com/questions/9864501/recordnotfound-with-accepts-nested-attributes-for-and-belongs-to/61905306#61905306) – Henry Jacob May 20 '20 at 04:49

2 Answers2

13

I got this to work by just adding an item_ids collection to the params (in addition to the items_attributes). You should just be able to massage your parameters in the controller to look like this

{ "utf8"=>"✓",
  "authenticity_token"=>"...",
  "payment"=>{
    "item_ids"=>[192]
    "items_attributes"=>{
      "0"=>{"billcode"=>"123", "id"=>"192"}
    },
  }
}

UPDATE 1: For some reason this only works if item_ids is before items_attributes in the hash. Have not reviewed Rails docs yet to figure out why.

steakchaser
  • 5,198
  • 1
  • 26
  • 34
  • This is actually pretty good. A lot of people posted more complicated solutions. I tried and works great. Updating the nested model and rendering validations in the form – Federico Feb 09 '15 at 15:00
  • Nice trick, what about saving an extra field such as position on the join table? Thanks! – José Pablo Orozco Marín Mar 12 '17 at 00:13
  • Awsome! This is working for me! Instead of using a hash inside `items_attributes`, an array of hashes can be used too. Just like this: `items_attributes: [{ "billcode"=>"123", "id"=>"192 }]` – MatayoshiMariano Jul 26 '17 at 13:28
1

This confused me...

"Adding existing records to a new record..."

So you have Item 1, 2, 3, and wish to associate them to a new Product object?

--

Join Model

The way to do this will be to use a join model (habtm) rather than sending the data through accepts_nested_attributes_for

The bottom line is every time you create a new Product object, its associated Item objects can only be associated to that product:

#items table
id | product_id | information | about | item | created_at | updated_at

So if you're looking to use existing Item objects, how can you define multiple associations for them? The fact is you can't - you'll have to create an intermediary table / model, often cited as a join model:

enter image description here

#app/models/product.rb
Class Product < ActiveRecord::Base
   has_and_belongs_to_many :items
end

#app/models/item.rb
Class Item < ActiveRecord::Base
   has_and_belongs_to_many :products
end

#items_products (table)
item_id | product_id

--

HABTM

If you use a HABTM setup (as I have demonstrated above), it will allow you to add / delete from the collection your various objects have, as well as a sneaky trick where you can just add Items to a product using item_ids:

#app/controllers/products_controller.rb
Class ProductsController < ApplicationController
   def create
      @product = Product.new(product_params)
      @product.save
   end

   private

   def product_params
       params.require(:product).permit(item_ids: [])
   end
end

If you then pass the param item_ids[] to your create_method, it will populate the collection for you.

If you want to add specific items to a product, or remove them, you may wish to do this:

#app/controllers/products_controller.rb
Class ProductsController < ApplicationController
   def add
     @product = Product.find params[:id]
     @item = Item.find params[:item_id]

     @product.items << @item
     @product.items.delete params[:item_id]
   end
end
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • thanks for your thoughtful answer. You got the question right. I see your point with the HABTM (I'm very familiar) as being a workaround. I'll give it a couple more days before marking this 'accepted'. It seems like a shortcoming in AR to not allow existing child records. As a temporary fix, I split the items collection from the params, updated them and created the Payment separately and then reattached them. – IAmNaN Jun 07 '14 at 23:09
  • I marked this as the selected answer because apparently changing the db schema is the only way get ActiveRecord to attach existing children to a new parent. – IAmNaN Jun 13 '14 at 19:40