Rails 4 + Carrierwave + jQuery文件上传+嵌套表单多文件问题

时间:2013-10-24 15:19:47

标签: ruby-on-rails ruby-on-rails-4 carrierwave nested-forms jquery-fileupload-rails

我正在处理我正在处理的rails应用程序的轻微问题。我正在使用Carrierwave,Nested_form,Simple_form和Jquery-file-upload宝石。

除数据外,大部分内容都运行良好。

我有两个模型,一个项目模型和一个附件模型。

提交项目表单时,所有文件都按原样上传,附件模型中的记录将按原样创建(每个文件一个)。但是对于项目模型,也会为每个文件创建一条记录。

我似乎无法弄清楚(在单个表单上,单个提交)如何只获得项目的一个记录和附件的多个记录。

任何帮助将不胜感激,我在下面概述了我的代码。如果可能的话,我想避免两个步骤,但如果有人能指出我正确的方向,那将有所帮助。

项目模型

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

附件模型

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

项目控制器

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

附件控制器

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

新项目表单(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>

2 个答案:

答案 0 :(得分:0)

我正在或多或少地处理完全相同的问题(我的涉及stock_items - 不是项目,但无论如何......)

一个问题是#fileupload.fileupload初始化整个表单 - 这将发布你不想要的项目

我还没有解决这个问题 - 但不知怎的'我们'必须让形式 POST

答案 1 :(得分:-1)

我认为您需要包含“父母”的索引。输入中的实例,在本例中为Project。所以:

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