0
votes

I have a problem with nested attributes. Creating works but when I update, the error message shows me that the values in the relation are not set. I can't find the reason.

The main model

class Product < ActiveRecord::Base
  has_many :product_options
  accepts_nested_attributes_for :product_options, 
   :allow_destroy => true, 
   :reject_if => proc { | r | r["name"].blank? or r["value"].blank? }
end

The nested model

class ProductOption < ActiveRecord::Base
  belongs_to :product
  validates :name, :presence => true
  validates :value, :presence => true
end

The controller is a bit shorted. Items is a model where Product is related to as has_one

class Admin::ProductsController < Admin::ApplicationController

  before_action :set_product, only: [ :new, :show, :edit, :update, :destroy ]

  def create
    @product = Product.new( product_params )
    respond_to do |format|
      if @product.save
        @product.product_options.build
        format.js { render :js => "alert( 'Daten gespeichert!' );" }
      else
        format.js { render :js => 'alert( "Fehler beim Speichern!" );' }
      end
    end
  end

  def update
    respond_to do |format|
      if @product.update( product_params )
        format.js { render :js => "alert( 'Daten gespeichert!' );" }
      else
        require "pp"
        pp @product.errors
        format.js { render :js => 'alert( "Fehler beim Speichern!" );' }
      end
    end
  end

  private

    # UPDATE: creating the product_options at this time
    # produces the described error :)
    def set_product
      @item = Item.find_by_id( params[ :item_id ] ) if params[ :item_id ]
      @product = @item.product ? @item.product : @item.build_product
      # WRONG Place for generating new options
      # 2.times { @product.product_options.build }
    end

    def product_params
      params.require( :product ).permit( :item_id, :name, :title, :active, :product_options_attributes => [ :id, :name, :value, :_destroy ] )
    end
end

The console output for the create is working and looks like

Started POST "/admin/items/653/product" for 127.0.0.1 at 2014-02-14 15:12:14 +0100
Processing by Admin::ProductsController#create as JS
  Parameters: {"utf8"=>"✓", "product"=>{"item_id"=>"653", "name"=>"1", "title"=>"1", "active"=>"1", "product_options_attributes"=>{"0"=>{"name"=>"aaa", "value"=>"aaaa"}}}, "commit"=>"Create Product", "item_id"=>"653"}
  User Load (1.5ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
   (0.6ms)  BEGIN
  SQL (17.5ms)  INSERT INTO "products" ("created_at", "item_id", "name", "title", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["created_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["item_id", 653], ["name", "1"], ["title", "1"], ["updated_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00]]
  SQL (1.3ms)  INSERT INTO "product_options" ("created_at", "name", "product_id", "updated_at", "value") VALUES ($1, $2, $3, $4, $5) RETURNING "id"  [["created_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["name", "aaa"], ["product_id", 28], ["updated_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["value", "aaaa"]]
  Item Load (1.0ms)  SELECT "items".* FROM "items" WHERE "items"."id" = $1 ORDER BY "items"."id" ASC LIMIT 1  [["id", 653]]
  ProductOption Load (1.3ms)  SELECT "product_options".* FROM "product_options" WHERE "product_options"."product_id" = $1  [["product_id", 28]]
  Rendered admin/products/_show.html.erb (7.6ms)
  Rendered admin/products/create.js.erb (9.2ms)
Completed 200 OK in 448ms (Views: 40.0ms | ActiveRecord: 27.1ms)

The the update. I doesn't work and gives an error that the nested fields are empty. It's the pp inside the update method

Started PATCH "/admin/items/653/product" for 127.0.0.1 at 2014-02-14 15:15:03 +0100
Processing by Admin::ProductsController#update as JS
  Parameters: {"utf8"=>"✓", "product"=>{"item_id"=>"653", "name"=>"1", "title"=>"1", "active"=>"1", "product_options_attributes"=>{"0"=>{"name"=>"aaa", "value"=>"aaaa", "id"=>"9"}, "1"=>{"name"=>"bbb", "value"=>"bbbb"}}}, "commit"=>"Update Product", "item_id"=>"653"}
  User Load (1.2ms)  SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
  Item Load (0.6ms)  SELECT "items".* FROM "items" WHERE "items"."id" = 653 LIMIT 1
  Product Load (0.9ms)  SELECT "products".* FROM "products" WHERE "products"."item_id" = $1 ORDER BY "products"."id" ASC LIMIT 1  [["item_id", 653]]
   (0.6ms)  BEGIN
  ProductOption Load (1.3ms)  SELECT "product_options".* FROM "product_options" WHERE "product_options"."product_id" = $1 AND "product_options"."id" IN (9)  [["product_id", 28]]
   (0.5ms)  ROLLBACK
#<ActiveModel::Errors:0x007f8bdeb9f818
 @base=
  #<Product id: 28, item_id: 653, content: nil, active: 1, created_at: "2014-02-14 14:12:14", updated_at: "2014-02-14 14:12:14", name: "1", title: "1", ordernumber: "">,
 @messages=
  {:"product_options.name"=>["can't be blank"],
   :"product_options.value"=>["can't be blank"]}>
Completed 200 OK in 18ms (Views: 0.1ms | ActiveRecord: 5.1ms)
1
Do you really have to build the association twice in your edit / update actions ? I think that the problem might be there.Guilherme Franco
To me it seems you build a new product option whether you create or update the product, and there could be a validation error as mentioned in your logAurelien Schlumberger
Maybe you could build product options only on create OR use a conditional statement with logic you want when you need to create product optionsAurelien Schlumberger

1 Answers

1
votes

I think I know what is the problem with your code. The accepts_nested_attributes_for does not require you to build any of the associated models. If the appropriate params are passed in then the model automatically builds or updates the associations.

In your case in the update method what you do is the following:

  • You find the relevant product. So far so good (although you could actually use a specific product id in your form)
  • Then you build two product options (in #set_product). This is the problem.
  • And in the end you update the model based on the parameters.

Now the problem with the second step is that you basically build two empty associated instances. Those are not affected by the accepts_nested_attributes. As a result you are trying to save 2+2 product options (the ones you build and the ones created by the params). Obviously you get the validation error due to the fact the two of the models have no attributes set.

You can make sure my hypothesis is correct by removing the validators from ProductOption. On update you should get 4 associated product options persisted.