Mic*_*ael 4 html file-upload ruby-on-rails-3 carrierwave
目前使用 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
Run Code Online (Sandbox Code Playgroud)
看法
<%= f.fields_for :images do |a| %>
<% if a.object.new_record? %>
<%= a.file_field :image, :multiple => true %><br>
<% end %>
<% end %>
Run Code Online (Sandbox Code Playgroud)
如果5.times { @ad.images.build }提供我的多个字段,显示 1 个接受多个字段的文件字段的正确方法是什么?
这似乎是一个流行的问题,没有很好的答案,所以我将在这里完全回答。在我开始之前,我会提到该代码可在https://github.com/mdchaney/multi 上获得,但请继续查看如何以最简单的方式执行此操作。
在我们开始之前,在 HTML 5 中,文件输入字段可以设置“multiple”属性。如果设置,结果与具有多个同名文件输入相同。在 Rails 中,为表单构建器构建的文件输入字段设置“multiple: true”将使其作为文件数组上传。
<%= f.file_field :files, :multiple => true %>
Run Code Online (Sandbox Code Playgroud)
变成
<input id="model_files" multiple="multiple" name="model[files][]" type="file" />
Run Code Online (Sandbox Code Playgroud)
其中“model”是您模型的名称。此输入控件将(至少在 Chrome 中)具有“选择文件”而不是“选择文件”的标签。
CarrierWave 本身无法处理此问题。它使用单个文本字段来存储有关单个文件的信息,并使用一些内部逻辑来确定该文件(以及可能的衍生文件)的存储位置。可以破解它以将多个文件的信息放在单个文本字段中,选择带有设置分隔符的编码。这将需要在 CarrierWave 上进行大量工作和黑客攻击。
不过,我不想破解 CarrierWave,所以问题变成了这样一个事实:将多个文件附加到一个项目实际上是一对多的关系,或者用 Rails 术语来说是“has_many”。因此,可以使用简单的属性编写器将文件输入字段中的文件添加到多个附加记录。
有了这个,我提出了最简单的方法,它使用带有多个属性集的 HTML 5 文件输入字段。有一些方法可以使用 jQuery 和 flash 来实现,但我提出这个是为了具体展示如何使用纯 HTML 5 来实现。
在我们的示例中,我们将有一个简单的“上传”模型,每个模型都有一个名称和任意数量的链接文件,这些文件将存储在另一个名为 links_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
Run Code Online (Sandbox Code Playgroud)
完成后,如果我们希望在字段上设置限制并添加“非空”约束:
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
Run Code Online (Sandbox Code Playgroud)
现在,让我们通过添加一个名为“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
Run Code Online (Sandbox Code Playgroud)
其中大部分是 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
Run Code Online (Sandbox Code Playgroud)
这是最简单的上传器,您可能希望限制上传的文件类型或其他什么。现在,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
Run Code Online (Sandbox Code Playgroud)
这没什么特别的,只是添加了 :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 %>
Run Code Online (Sandbox Code Playgroud)
我添加了两段代码。如果@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 %>
Run Code Online (Sandbox Code Playgroud)
在这里,我只是为“文件”添加了一个标题和一个显示所有文件并提供查看链接的表格。没什么特别的,但它有效。
如果我把它变成一个真正的应用程序,我可能还会在上传索引页面上提供一个文件列表或最少的文件计数。
就是这样了。同样,如果你想下载整个测试应用程序,可以在 github 上找到它,但我已经把我的整个 Rails 生成语句和更改放在这篇文章中。