Alain Goldman
Alain Goldman

Reputation: 2908

Submitting to two models with one form with jQuery file upload

I have a photo and product model. Photo belong to product and product has_many photos. Also product accepts_nested_attributes_for photos. I have a form that creates both the product and photo at the same time. I'm using jQuery-file-upload gem which gives a nice thumbnail and a start cancel button. The problem is that when I click the start upload button it submits the whole goddam form asynchronously.

This is a problem because:

  1. If I try to upload 3 photos and press upload 3 times it will make 3 different products each with only 1 photo.
  2. If they dont fill in product details clicking upload photo will just attempt to submit it with nil product values

HALP!

product form

= form_for @product,:url => products_path, :html => { id: "fileupload", multipart: true } do |f| 
    = f.text_field :name, placeholder: "Name"
    %br
    = f.text_field :price, class: "auto", data: { a_sign: "$ " }, placeholder: "Price" 
    %br
    = f.fields_for :photos do  |fp| 
      =fp.file_field :image
    %br

  .files{"data-target" => "#modal-gallery", "data-toggle" => "modal-gallery"}
  %p.button.start
    = f.submit

product controller

  def new 
    @product = Product.new
    @product.photos.build
  end



  def create
  @product = current_user.products.create(params[:product])
  @photo = Photo.new(params[:photo])
    respond_to do |format|
      if @product.save
        format.html {
          render :json => [@photo.to_jq_image].to_json,
          :content_type => 'text/html',
          :layout => false
        }
        format.json { render json: {files: [@photo.to_jq_image]}, status: :created, location: @photo }
      else
        format.html { render action: "new" }
        format.json { render json: @photo.errors, status: :unprocessable_entity }
      end
    end
  end

method: to_jq_image

 def to_jq_image
   {
     "name" => read_attribute(:image_file_name),
     "size" => read_attribute(:image_file_size),
     "url" => image.url(:original),
     "delete_type" => "DELETE" 
   }
 end

console output

 SQL (1.2ms)  INSERT INTO "products" ("created_at", "name", "price", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)  [["condition", "a good conditon"], ["created_at", Sun, 14 Jul 2013 17:58:19 UTC +00:00], ["name", "An example name"], ["price", #<BigDecimal:b2ab920,'0.125E1',18(18)>], ["updated_at", Sun, 14 Jul 2013 17:58:19 UTC +00:00], ["user_id", 6]]
Binary data inserted for `string` type on column `image_content_type`
  SQL (1.8ms)  INSERT INTO "photos" ("created_at", "image_content_type", "image_file_name", "image_file_size", "product_id", "updated_at") VALUES (?, ?, ?, ?, ?, ?)  [["created_at", Sun, 14 Jul 2013 17:58:19 UTC +00:00], ["image_content_type", "image/jpeg"], ["image_file_name", "DWadeCrop.jpg"], ["image_file_size", 97742], ["product_id", 140], ["updated_at", Sun, 14 Jul 2013 17:58:19 UTC +00:00]]
   (317.0ms)  commit transaction
   (0.1ms)  begin transaction
   (0.1ms)  commit transaction
Completed 201 Created in 849ms (Views: 2.4ms | ActiveRecord: 320.6ms)

product && photo models

  class Product < ActiveRecord::Base
  attr_accessible :description, :name, :price, :photo, :photos_attributes
  has_many :photos, dependent: :destroy
  accepts_nested_attributes_for :photos, allow_destroy: true

  include Rails.application.routes.url_helpers

  belongs_to :user

  validates :user_id, presence: true
  validates :name, presence: true, length:  { minimum: 5 }
  validates :price, presence: true
  validates :description, presence: true, length:  { minimum: 5 }

end

class Photo < ActiveRecord::Base
  attr_accessible :image
  has_attached_file :image

  include Rails.application.routes.url_helpers

  belongs_to :product
    validates_attachment :image, presence: true,
      content_type: { content_type: ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'] },
      size: { less_than: 5.megabytes }
    has_attached_file :image, styles: { medium: "320x240>", :thumb => "100x100>"}

end

Upvotes: 0

Views: 257

Answers (1)

Cauterise
Cauterise

Reputation: 527

Forget the way your doing it. Instead what you need to do is to split the forms up and put the one create photo form on top and remove the submit button. Then when the create product happens you look for all the photos the user has updated and that is not associated to the current product id

new product page

= form_for @photo, :html => { :multipart => true, :id => "fileupload"  } do |f|
  %span Add files...
    = f.file_field :image
%br
= form_for @product,:url => products_path, :html => { id: "fileupload", multipart: true } do |f| 
  %p
    = f.text_field :name, placeholder: "Name"
  %p
    = f.text_field :price, class: "auto", data: { a_sign: "$ " }, placeholder: "Price" 
  %p.button.start
    = f.submit

product controller

  def new 
    Photo.where(:product_id => nil, :user_id => current_user).delete_all
    @product = Product.new
    @photo = Photo.new
    raise "foo"
  end

  def create
  @product = current_user.products.create(params[:product])
    if @product.save
      Photo.where(:product_id => nil, :user_id => current_user).update_all(:product_id => @product.id) 
      render "show", notice: "Product created!"
    else
      render "new", error: "Error submitting product"
    end
  end

photo controller

def create
  @photo = Photo.new(params[:photo])
    respond_to do |format|
      @photo.user_id = current_user.id
      if @photo.save
        format.html {
          render :json => [@photo.to_jq_image].to_json,
          :content_type => 'text/html',
          :layout => false
        }
        format.json { render json: {files: [@photo.to_jq_image]}, status: :created, location: @photo }
      else
        format.html { render action: "new" }
        format.json { render json: @photo.errors, status: :unprocessable_entity }
      end
    end
  end

Upvotes: 1

Related Questions