4

目前使用 Rails 3.2 和 Carrierwave。

我有多个文件设置,但它需要多个文件字段,但我只需要一个文件字段。如果浏览器不支持 HTML5 多重属性,我会将其作为默认设置。

控制器

def new
   @ad = Ad.new
   5.times { @ad.images.build } // provides multiple file fields in the view.
end

def create
  ad = Ad.new(params[:ad])
  user = User.find(session[:user_id])
  if user.ads << ad
    flash[:notice] = "Ad successfully saved."
    redirect_to ad_listing_path(ad.id, ad.slug)
  else
    render :new, :alert => "Ad was not saved."
  end
end

看法

<%= f.fields_for :images do |a| %>
  <% if a.object.new_record? %>
     <%= a.file_field :image, :multiple => true %><br>
  <% end %>
<% end %>

如果5.times { @ad.images.build }提供我的多个字段,显示 1 个接受多个字段的文件字段的正确方法是什么?

4

3 回答 3

5

这似乎是一个没有好的答案的热门问题,所以我将在这里完全回答它。在开始之前,我会提到代码可在https://github.com/mdchaney/multi获得,但请继续了解如何以最简单的方式执行此操作。

在我们开始之前,在 HTML 5 中,文件输入字段可以设置“多个”属性。如果已设置,则结果与具有多个同名文件输入的结果相同。在 Rails 中,为表单构建器构建的文件输入字段设置“multiple: true”将导致它作为文件数组上传。

<%= f.file_field :files, :multiple => true %>

变成

<input id="model_files" multiple="multiple" name="model[files][]" type="file" />

其中“模型”是您的模型的名称。这个输入控件将(至少在 Chrome 中)具有“选择文件”而不是“选择文件”的标签。

CarrierWave 本身无法处理此问题。它使用单个文本字段来存储有关单个文件的信息,并使用一些内部逻辑来确定该文件(以及可能的派生文件)的存储位置。可以破解它以将多个文件的信息放在单个文本字段中,选择带有设置分隔符的编码。这需要对 CarrierWave 进行大量工作和破解。

不过,我不想破解 CarrierWave,所以问题变成了这样一个事实:将多个文件附加到一个项目实际上是一对多的关系,或者在 Rails 术语中是“has_many”。因此,可以使用简单的属性编写器将文件输入字段中的文件添加到多个附加记录。

有了这个,我提出了最简单的方法,即使用具有多个属性集的 HTML 5 文件输入字段。有很多方法可以使用 jQuery 和 flash 来实现,但我提出这个是为了专门展示如何使用直接的 HTML 5 来实现。

在我们的示例中,我们将有一个简单的“上传”模型,每个模型都有一个名称和任意数量的链接文件,这些文件将存储在另一个名为linked_files 的模型中(让它变得简单,对吗?)。linked_file 将保存原始文件名、提供的内容类型,当然还有 CarrierWave 用于存储其信息的字段。

让我们为上传创建脚手架,然后为linked_files创建模型:

rails g scaffold Upload name:string
rails g model LinkedFile upload:references filename:string mime_type:string file:string

完成后,我们可以在字段上设置限制并添加“非空”约束:

class CreateUploads < ActiveRecord::Migration
  def change
    create_table :uploads do |t|
      t.string :name, limit: 100, null: false

      t.timestamps
    end
  end
end


class CreateLinkedFiles < ActiveRecord::Migration
  def change
    create_table :linked_files do |t|
      t.references :upload, null: false
      t.string :filename, limit: 255, null: false
      t.string :mime_type, limit: 255, null: false
      t.string :file, limit: 255, null: false

      t.timestamps
    end
    add_index :linked_files, :upload_id
  end
end

现在,让我们通过添加一个名为“files”的新属性编写器来修复 Upload 模型:

class Upload < ActiveRecord::Base
  has_many :linked_files, inverse_of: :upload, dependent: :destroy
  accepts_nested_attributes_for :linked_files, reject_if: :all_blank, allow_destroy: true
  validates_associated :linked_files

  attr_accessible :name, :files, :linked_files_attributes

  def files=(raw_files)
    raw_files.each do |raw_file|
      self.linked_files.build({filename: raw_file.original_filename, mime_type: raw_file.content_type, file: raw_file})
    end
  end

  validates :name, presence: true, length: { maximum: 100 }
end

其中大部分是 Rails 模型的正常声明。这里唯一真正添加的是“files=”方法,它采用数组中的一组上传文件并为每个文件创建一个“linked_file”。

我们需要一个 CarrierWave 上传器:

class FileUploader < CarrierWave::Uploader::Base

  storage :file

  def store_dir
    "uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
  end

end

这是最简单的上传器,您可能希望限制上传的文件类型或其他。现在,LinkedFile 模型:

class LinkedFile < ActiveRecord::Base
  mount_uploader :file, FileUploader

  belongs_to :upload, inverse_of: :linked_files

  attr_accessible :file, :filename, :mime_type, :file_cache, :remove_file

  validates :filename, presence: true, length: { maximum: 255 }
  validates :mime_type, presence: true, length: { maximum: 255 }
end

这并没有什么特别之处,只是添加了 :file_cache 和 :remove_file 作为文件上传器的可访问属性。

除了视图,我们现在完成了。我们实际上只需要更改表单,但我们还将更改“显示”以允许访问上传的文件。这是 _form.html.erb 文件:

<%= form_for(@upload, { multipart: true }) do |f| %>
  <% if @upload.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@upload.errors.count, "error") %> prohibited this upload from being saved:</h2>

      <ul>
      <% @upload.errors.full_messages.each do |msg| %>
        <li><%= msg %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
  </div>

  <% if f.object.linked_files.size == 0 -%>

    <div class="field">
      <%= f.label :files %><br />
      <%= f.file_field :files, :multiple => true %>
    </div>

  <% else -%>

    <fieldset>
      <legend>Linked Files</legend>
      <%= f.fields_for :linked_files do |lff| -%>
        <div class="field">
          <%= lff.label :filename %><br />
          <%= lff.text_field :filename %>
        </div>
        <div class="field">
          <%= lff.label :mime_type %><br />
          <%= lff.text_field :mime_type %>
        </div>
        <div class="field">
          <%= lff.label :file, 'File (replaces current selection)' %><br />
          <%= lff.file_field :file %>
          <%= lff.hidden_field :file_cache %>
        </div>
        <div class="field">
          <%= lff.check_box :_destroy %>
          Remove this file
        </div>
        <hr />
      <% end -%>
    </fieldset>

  <% end -%>

  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

我添加了两段代码。如果@upload 对象没有与之关联的“linked_files”,我只显示多文件输入。否则,我会显示每个linked_file 及其所有信息。可以将“文件”方法添加到 Upload 并以这种方式处理它,但这样做会丢失跨请求的 mime 类型。

您可以轻松地对此进行测试,因为上传的“名称”是必填字段。启动服务器并http://127.0.0.1:3000/uploads查看应用程序。单击“新建”链接,选择一些文件并点击“创建上传”而不提供名称。下一页将显示您正在等待的所有文件。添加名称时,所有内容都会保存。让我们修改“show”操作以显示linked_files:

<p id="notice"><%= notice %></p>

<p>
  <b>Name:</b>
  <%= @upload.name %>
</p>

<p>
  <b>Files:</b><br />
</p>

<table>
  <thead><tr><th>Original Filename</th><th>Content Type</th><th>Link</th></tr></thead>
  <tbody>
    <% @upload.linked_files.each do |linked_file| -%>
      <tr>
        <td><%= linked_file.filename %></td>
        <td><%= linked_file.mime_type %></td>
        <td><%= link_to linked_file.file.url, linked_file.file.url %></td>
      </tr>
    <% end -%>
  </tbody>
</table>

<%= link_to 'Edit', edit_upload_path(@upload) %> |
<%= link_to 'Back', uploads_path %>

在此,我只是为“文件”添加了一个标题和一个显示所有文件并提供查看链接的表格。没什么花哨的,但它确实有效。

如果我把它变成一个真正的应用程序,我可能还会在上传索引页面上提供一个文件列表或最少的文件计数。

就是这样了。同样,如果您想下载整个测试应用程序,可以在 github 上找到它,但我已将我的 Rails 生成语句和更改的全部内容放在这篇文章中。

于 2014-02-19T13:05:43.980 回答
2

HTML 并不真正支持一个文件字段的多次上传。您可以使用一些 JavaScript 插件来解决它。想到的两个:

  1. 上传
  2. jQuery 文件上传
于 2012-07-06T07:33:37.527 回答
0

不幸的是,CarrierWave 不支持 HTML5 多重属性(目前)。

https://github.com/carrierwaveuploader/carrierwave/issues/984

于 2013-07-22T14:44:27.173 回答