Rails 3.2 HTML5多文件上传,无需Javascript的Carrierwave实现

4

目前使用的是Rails 3.2和Carrierwave。

我有多个文件设置,但需要多个文件字段,但我只想要一个文件字段。如果浏览器不支持HTML5的multiple属性,我将提供它作为默认值。

控制器

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 }提供了多个字段,那么展示一个可接受多个文件的文件字段的正确方式是什么?
3个回答

5
这似乎是一个普遍的问题,但没有好的答案,所以我将在这里完全回答它。在开始之前,我要提到代码可在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生成语句和更改。

2

HTML并不真正支持一个文件字段的多次上传。您可以通过一些JavaScript插件来解决这个问题。有两个我想到的:

  1. Uploadify
  2. jQuery文件上传

2
它在HTML5中得到支持,这正是他的问题所涉及的。 - Jason L Perry

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接