Search code examples
ruby-on-railsrubyjquery-file-upload

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

Solution

  • 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