这似乎是一个普遍的问题,但没有好的答案,所以我将在这里完全回答它。在开始之前,我要提到代码可在
https://github.com/mdchaney/multi上找到,但请跟随以下步骤,以最简单的方式完成此操作。
在进入详细内容之前,需了解HTML5的文件输入字段可以设置“multiple”属性。如果设置了该属性,则结果与具有相同名称的多个文件输入相同。在Rails中,对于表单构建器生成的文件输入字段设置“multiple: true”将导致其上传为文件数组。
<%= f.file_field :files, :multiple => true %>
变成
<input id="model_files" multiple="multiple" name="model[files][]" type="file" />
“model”是您的模型名称。在Chrome浏览器中,此输入控件的标签将显示为“选择文件”,而不是“选择文件”。
CarrierWave无法本地处理这个问题。它使用单个文本字段存储有关单个文件的信息,并使用一些内部逻辑来确定该文件(及其可能的衍生品)存储在何处。可以通过选择具有设置定界符的编码来将多个文件的信息放入单个文本字段中进行黑客攻击。这需要对CarrierWave进行大量的工作和黑客攻击。
我不想黑进CarrierWave,因此问题变成了一个事实:将多个文件附加到一个项目实际上是一对多的关系,或者在Rails术语中是“has_many”。因此,可以使用简单的属性编写器将来自文件输入字段的文件添加到多个已附加记录中。
有了这个,我提供了使用设置了多个属性的HTML 5文件输入字段完成此操作的最简单方法。有用jQuery和flash的方法,但我提供这个是为了特别展示如何使用纯HTML 5来完成它。
在我们的示例中,我们将拥有一个名为“uploads”的简单模型,每个模型都将具有名称和任意数量的链接文件,这些文件将存储在另一个称为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 作为可访问的属性。
除了视图之外,我们已经完成了所有工作。我们真正需要改变的只是表单,但我们也会更改“show”,以允许访问上传的文件。以下是 _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及其所有信息。可以向上传对象添加一个“files”方法并以此处理,但这样做会导致跨请求失去mime类型。
你可以轻松测试此功能,因为上传“名称”是必填字段。启动服务器并转到
http://127.0.0.1:3000/uploads
,以查看应用程序。单击“new”链接,选择一些文件,然后点击“Create Upload”而不提供名称。下一页将显示所有等待上传的文件。当您添加名称时,所有内容都将保存。让我们修改“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生成语句和更改。