Richard D Walsh
Richard D Walsh

Reputation: 153

Rails 4 + Carrierwave + jQuery File Upload + Nested Form Multiple File Issue

I am having a slight issue with a rails app I am working on. I am utilizing Carrierwave, Nested_form, Simple_form, and Jquery-file-upload gems.

The majority of everything is working fine, with the exception of the data.

I have two models, a Project Model and an Attachments Model.

When the Project form is submitted, all the files upload as they should, a record in the attachments model is created as it should (one per file). But as for the projects model, a record is created for each file as well.

I can't seem to figure out (on a single form, with single submit) how to get only one record for the project and multiple records for the attachments.

Any help would be appreciated, I've outlined my code below. I'd like to avoid a two step process if possible, but if someone could point me in the right direction that would help.

Project Model

class Project < ActiveRecord::Base
  has_many :attachments, :dependent => :destroy

  accepts_nested_attributes_for :attachments, :allow_destroy => true

  def generate_token
    self.token = loop do
      random_token = SecureRandom.urlsafe_base64
      break random_token unless Project.where(token: random_token).exists?
    end
  end
end

Attachment Model

class Attachment < ActiveRecord::Base
  belongs_to :project, :polymorphic => true

  include Rails.application.routes.url_helpers

  mount_uploader :file, AttachmentUploader

  def to_jq_upload
  {
    "name" => read_attribute(file),
    "url" => file.url,
    "size" => file.size,
    "delete_url" => attachment_path(:id => id),
    "delete_type" => "DELETE"
  }
  end
end

Projects Controller

class ProjectsController < ApplicationController
  def index
  @projects = Project.all

  respond_to do |format|
    format.html
    format.json { render json: @projects }
  end
end

def new
  @project = Project.new
  @project.token = @project.generate_token
  @attachments = @project.attachments.build
end

def create
  @project = Project.new(project_params)
  respond_to do |format|
    if @project.save
      format.html { redirect_to projects_url, notice: 'Project was successfully created.' }
      format.json { render json: @project, status: :created, location: @project }
    else
      format.html {}
      format.json {}
    end
  end
end

def destroy
  @project = Project.find(params[:id])
  @project.destroy

  respond_to do |format|
    format.html { redirect_to projects_url }
    format.json { head :no_content }
  end
end

private
  def project_params
    params.require(:project).permit(:name, :token, :number_of_pages, :number_of_copies, :flat_page_size, :trim_page_size, :purchase_order_number, :preferred_delivery_date, :delivery_method, :delivery_instructions, :project_instructions, attachments_attributes: [:id, :attachment, :name, :filename, :file, :project_token, :branch])
  end
end

Attachments Controller

class AttachmentsController < ApplicationController
before_filter :the_project

def index
  @attachments = Attachment.where("project_id = ?", the_project)
  render :json => @attachments.collect { |p| p.to_jq_upload }.to_json
end

def create
  @project = Project.find(params[:project_id])
  @attachment = Attachment.new(attachment_params)

  if @attachment.save
    respond_to do |format|
      format.html { render :json => [@attachment.to_jq_upload].to_json, :content_type => 'text/html', :layout => false }
      format.json { render :json => {files: [@attachment.to_jq_upload]}.to_json }
    end
  else
    render :json => [{ :error => "custom_failure "}], :status => 304
  end
end

def destroy
  @attachment = Attachment.find(params[:id])
  @attachment.destroy

  render :json => true
end

private

  def the_project
    @project = Project.find(params["project_id"])
  end

end

New Project Form (app/views/projects/new.html.erb)

<h2>New Project</h2>
<br />

<%= simple_nested_form_for @project, :defaults => { :wrapper_html => {:class => 'form-group'}, :input_html => { :class => 'form-control' } }, :html => { :multipart => true,  :id => "fileupload", :class => 'horizontal-form', :role => "form" } do |f| %>

<div class="row">
  <div class="col-lg-12">
    <%= f.input :name, :label => "Project Name / Description", :class => 'col-lg-12' %>
    <%= f.hidden_field :token %>
  </div>
</div>
<div class="row">
  <div class="col-lg-6">
    <%= f.input :number_of_pages %>
    <%= f.input :flat_page_size %>
    <%= f.input :purchase_order_number %>
  </div>

  <div class="col-lg-6">
    <%= f.input :number_of_copies %>
    <%= f.input :trim_page_size, :label => 'Finished Size <em><small>(If Different from Flat Page Size)</small></em>' %>
    <%= f.input :preferred_delivery_date, :as => :text %>
  </div>
</div>

<div class="row">
  <div class="col-lg-6">
    <%= f.input :delivery_method %>
  </div>

  <div class="col-lg-6">
    <%= f.input :project_instructions %>
  </div>
</div>

<div class="row">
  <div class="col-lg-6">
    <select id="branches">
      <option>Calgary Downtown</option>
      <option>Calgary South</option>
      <option>Edmonton</option>
      <option>Kelowna</option>
    </select>
  </div>
</div>
<br />
<div class="row fileupload-buttonbar">
  <div class="col-lg-7">
    <%= fields_for :attachments do |a| %>
      <span class="btn btn-success fileinput-button">
        <i class="glyphicon glyphicon-plus"></i>
        <span>Add files...</span>
        <%= a.file_field :file, :name => 'project[attachments_attributes][0][file]', :multiple => true %>      
      </span>

      <button type="submit" class="btn btn-primary start">
        <i class="glyphicon glyphicon-upload"></i>
        <span>Start Upload</span>
      </button>

      <button type="reset" class="btn btn-warning cancel">
        <i class="glyphicon glyphicon-ban-circle"></i>
        <span>Cancel Upload</span>
      </button>

      <button type="button" class="btn btn-danger delete">
        <i class="glyphicon glyphicon-trash"></i>
        <span>Delete Upload</span>
      </button>

       <%= a.hidden_field :branch, :value => "Calgary Downtown" %>
       <%= a.hidden_field :project_token, :value => @project.token %>
      <% end %>
    </div>

    <div class="col-lg-5">
      <div class="progress progress-success progress-striped active fade">
      <div class="bar" style="width:0%"></div>
    </div>
  </div>

</div>

<div class="row fileupload-loading"></div>

  <div class="row">
    <table class="table table-striped">
      <tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery">
      </tbody>
    </table>
  </div>
<% end %>

<script>
  var fileUploadErrors = {
    maxFileSize: 'File is too big',
    minFileSize: 'File is too small',
    acceptFileTypes: 'Filetype not allowed',
    maxNumberOfFiles: 'Max number of files exceeded',
    uploadedBytes: 'Uploaded bytes exceed file size',
    emptyResult: 'Empty file upload result'
  }; 
</script>

<!-- The template to display files available for upload -->
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
  <tr class="template-upload fade">
      <td class="preview"><span class="fade"></span></td>
      <td class="name"><span>{%=file.name%}</span></td>
      <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
        {% if (file.error) { %}
          <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
        {% } else if (o.files.valid && !i) { %}
          <td>
              <div class="progress progress-success progress-striped active"><div class="bar" style="width:0%;"></div></div>
          </td>
          <td class="start">{% if (!o.options.autoUpload) { %}
              <button class="btn btn-primary">
                  <i class="icon-upload icon-white"></i>
                  <span>{%=locale.fileupload.start%}</span>
              </button>
          {% } %}</td>
      {% } else { %}
          <td colspan="2"></td>
      {% } %}
      <td class="cancel">{% if (!i) { %}
          <button class="btn btn-warning">
              <i class="icon-ban-circle icon-white"></i>
              <span>{%=locale.fileupload.cancel%}</span>
          </button>
      {% } %}</td>
  </tr>
{% } %}
</script>
<!-- The template to display files available for download -->
<script id="template-download" type="text/x-tmpl">
  {% for (var i=0, file; file=o.files[i]; i++) { %}
  <tr class="template-download fade">
    {% if (file.error) { %}
      <td></td>
      <td class="name"><span>{%=file.name%}</span></td>
      <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
      <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
      {% } else { %}
          <td class="preview">{% if (file.thumbnail_url) { %}
              <a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a>
          {% } %}</td>
          <td class="name">
              <a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a>
          </td>
          <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
          <td colspan="2"></td>
      {% } %}
      <td class="delete">
          <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
              <i class="icon-trash icon-white"></i>
              <span>{%=locale.fileupload.destroy%}</span>
          </button>
          <input type="checkbox" name="delete" value="1">
      </td>
  </tr>
{% } %}
</script>

<script type="text/javascript" charset="utf-8">
  $(function () {
    var num_added = 0;
    var added = 0;
    var all_data = {};

    $('#branches').change(function() {

        var test = $("option:selected",this).text();
        $('#project_attachments_attributes_0_branch').val(test);

     });

    // Initialize the jQuery File Upload widget:
    $('#fileupload').fileupload({
      sequentialUploads: true,
    });

   });
 </script>

Upvotes: 4

Views: 3832

Answers (2)

Perri
Perri

Reputation: 1

I believe you need to include the index of the 'parent' instance in the input, in this case, Project. So:

 <%= a.file_field :file, :name => 'project[*index*][attachments_attributes][0][file]', :multiple => true %> 

Upvotes: -1

walt_die
walt_die

Reputation: 630

I'm working on more or less that exact same issue (mine involves stock_items - not projects, but anyways...)

One issue is the #fileupload.fileupload which initializes the entire form - and that will post your project, which you will not want to

I've not solved that one yet - but somehow 'we' have to make the parent form not POST

Upvotes: 0

Related Questions