Nested Form not creating the record thats nested.

2019-07-12 19:21发布

问题:

Hello I have 2 models Product and ProductSize. I create a ProductSize in an Product form.

Problem is, its not persisting creating the ProductSizes. You should be able to go product.product_sizes and a list of ProductSizes show.

product.rb

class Product < ActiveRecord::Base
  acts_as_taggable

  belongs_to :user
  belongs_to :category

  has_many :product_sizes
  has_many :product_images, :dependent => :destroy

  validates :title, presence: true, length: { maximum: 30 }
  validates :description, presence: true, length: { maximum: 2000 }
  validates :category, :user, :price, presence: true

  accepts_nested_attributes_for :product_images, :product_sizes, allow_destroy: true
end

product_size.rb

class ProductSize < ActiveRecord::Base
  belongs_to :product
  belongs_to :size

  validates :quantity, presence: true
end

Here is my form. The way it works is: User can upload images, then select what category the Product is for. Lets say they select Shirt, then a list of all shirt sizes will drop down like XS, Small, Medium, Large. Then the user puts what quantity they have for the sizes they have. Like 13 XS shirts and 4 large Shirts.

<%= javascript_include_tag "custom" %>
<div class="container">
  <div class=“row”>
    <div class="col-md-6 col-md-offset-3">
      <div class="panel panel-primary">
        <div class="panel-body">
          <%= simple_nested_form_for @product do |f| %>
            <%= f.fields_for :product_images do |product_image| %>
              <% if product_image.object.new_record? %>
                <%= product_image.file_field(:product_image) %>
                <%= product_image.link_to_remove "Remove Image", data: { confirm: "Are you sure you want to delete this image?" } %>
              <% else %>
                <%= product_image.hidden_field :_destroy %>
              <% end %>
            <% end %>
            <p><%= f.link_to_add "Add a image", :product_images, :data => { :product_image => "#product_images" } %></p>
            <%= f.collection_select :category_id, @categories, :id, :name, include_blank: true, prompt: "Select One Category" %>

            <% @categories.each do |category| %>
              <div class='sizes_container' id ='sizes_container_for_<%= category.id %>'>
                <% category.sizes.each do |size| %>
                  <%= label_tag "product_form[sizes_by_id][#{size.id}]", size.title %>
                  <%= text_field_tag "product_sizes_attributes[sizes_quantity][#{size.id}]" %>
                <% end %>
              </div>
            <% end %>

            <%= f.input :title, label:"Title"%>
            <%= f.input :price, label:"Price"%>
            <%= f.input :description,label:"Description" %>
            <%= f.input :size_description, label:"Size Details"%>
            <%= f.input :shipping_description, label:"Shipping Details"%>
            <%= f.input :tag_list,label:"Tags - Seperate tags using comma ','. 5 tags allowed per product" %>
            <%= f.button :submit, "Create new product", class: "btn-lg btn-success" %>
          <% end %>
        </div>
      </div>
    </div>
  </div>
</div>

Here are what my params look like at the create action.

    36: def create
 => 37:   binding.pry
    38:   @product = Product.new product_params
    39:   @product.user_id = current_user.id
    40:   if @product.save
    41:     redirect_to @product
    42:     flash[:success] = "You have created a new product"
    43:   else
    44:     flash[:danger] = "Your product didn't save"
    45:     render "new"
    46:   end
    47: end

**[1] pry(#<ProductsController>)> product_params**
=> {"title"=>"test",
 "price"=>"3325",
 "description"=>"test",
 "tag_list"=>"test",
 "category_id"=>"3",
 "size_description"=>"test",
 "shipping_description"=>"test",
 "product_images_attributes"=>
  {"0"=>
    {"product_image"=>
      #<ActionDispatch::Http::UploadedFile:0x007f8cb786a010
       @content_type="image/jpeg",
       @headers="Content-Disposition: form-data; name=\"product[product_images_attributes][0][product_image]\"; filename=\"780069_black_l.jpg\"\r\nContent-Type: image/jpeg\r\n",
       @original_filename="780069_black_l.jpg",
       @tempfile=#<File:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151223-46388-p07o84.jpg>>,
     "_destroy"=>"false"},
   "1450863732810"=>
    {"product_image"=>
      #<ActionDispatch::Http::UploadedFile:0x007f8cb7869e08
       @content_type="image/jpeg",
       @headers="Content-Disposition: form-data; name=\"product[product_images_attributes][1450863732810][product_image]\"; filename=\"20090a.jpg\"\r\nContent-Type: image/jpeg\r\n",
       @original_filename="20090a.jpg",
       @tempfile=#<File:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151223-46388-n9mzf2.jpg>>,
     "_destroy"=>"false"}}}

[2] pry(#<ProductsController>)> params
=> {"utf8"=>"✓",
 "authenticity_token"=>"jfh6vsb1N1zhAIFyzer4liwuV+iHQ+P8pF6mZHUyF8IXNn6oXqnLDse84jnrP3BKI889CWigIDqVMJncxOYZ9Q==",
 "product"=>
  {"product_images_attributes"=>
    {"0"=>
      {"product_image"=>
        #<ActionDispatch::Http::UploadedFile:0x007f8cb786a010
         @content_type="image/jpeg",
         @headers="Content-Disposition: form-data; name=\"product[product_images_attributes][0][product_image]\"; filename=\"780069_black_l.jpg\"\r\nContent-Type: image/jpeg\r\n",
         @original_filename="780069_black_l.jpg",
         @tempfile=#<File:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151223-46388-p07o84.jpg>>,
       "_destroy"=>"false"},
     "1450863732810"=>
      {"product_image"=>
        #<ActionDispatch::Http::UploadedFile:0x007f8cb7869e08
         @content_type="image/jpeg",
         @headers="Content-Disposition: form-data; name=\"product[product_images_attributes][1450863732810][product_image]\"; filename=\"20090a.jpg\"\r\nContent-Type: image/jpeg\r\n",
         @original_filename="20090a.jpg",
         @tempfile=#<File:/var/folders/yx/znmx6qfj0c507bvkym6lvhxh0000gn/T/RackMultipart20151223-46388-n9mzf2.jpg>>,
       "_destroy"=>"false"}},
   "category_id"=>"3",
   "title"=>"test",
   "price"=>"3325",
   "description"=>"test",
   "size_description"=>"test",
   "shipping_description"=>"test",
   "tag_list"=>"test"},
 "product_sizes_attributes"=>{"sizes_quantity"=>{"1"=>"3", "2"=>"4", "3"=>""}},
 "commit"=>"Create new product",
 "controller"=>"products",
 "action"=>"create"}

[3] pry(#<ProductsController>)> params[:product_sizes_attributes]
=> {"sizes_quantity"=>{"1"=>"3", "2"=>"4", "3"=>""}}

回答1:

Looks like your form doesn't have the fields_for for the product_sizes:

<%= simple_nested_form_for @product do |f| %>
   <%= f.fields_for :product_sizes do |product_size| %>
       <%= product_size.text_field .... %>
   <% end %>
<% end %>

This will have to be backed up with the appropriate controller code:

#app/controllers/products_controller.rb
class ProductsController < ApplicationController
   def new
      @product = Product.new
      @product.product_images.build
      @product.product_sizes.build
   end

   def create
      @product = Product.new product_params
      @product.save
   end

   private

   def product_params
      params.require(:product).permit(:x, :y, :z, product_images_attributes: [:image], product_sizes_attributes: [...])
   end
end

This should get it working for you.



回答2:

Use fields_for for project_size. Get the total @sizes from @categories and use collection_select like you have used previously. Your project_size form code

<% @categories.each do |category| %>
   <div class='sizes_container' id ='sizes_container_for_<%= category.id %>'>
   <% category.sizes.each do |size| %>
     <%= label_tag "Size"product_form[sizes_by_id][#{size.id}]", size.title %>
     <%= text_field_tag "product_sizes_attributes[sizes_quantity][#{size.id}]" %>
   <% end %>
   </div>
<% end %>

will be

<% @sizes = Size.joins(:category).where('categories.id IN (?)', @categories.map(&:id)) %>

<%= f.fields_for :product_sizes do |product_size| %>
   <%= label_tag "Size" %>
   <%= product_size.collection_select :size_id, @sizes, :id, :title, include_blank: true, prompt: "Select One Size" %>
   <%= product_size.text_field :quantity %>
<% end %>

And product_params will be

params.require(:product).permit(:title, :price, :description, :tag_list,:category_id, :size_description, :shipping_description, product_images_attributes: [:id, product_image: [] ], product_sizes_attributes: [:id, :size_id, :quantity])

I hope this would be helpful.