Rails 4使用carrierwave上传多个图像或文件

Posted

技术标签:

【中文标题】Rails 4使用carrierwave上传多个图像或文件【英文标题】:Rails 4 multiple image or file upload using carrierwave 【发布时间】:2014-02-20 03:05:47 【问题描述】:

如何使用 Rails 4 和 CarrierWave 从文件选择窗口上传多个图像?我有一个 post_controllerpost_attachments 模型。我该怎么做?

有人可以举个例子吗?有没有简单的方法?

【问题讨论】:

【参考方案1】:

这是使用 rails 4 中的载波从头开始上传多个图像的解决方案

或者你可以找到工作演示: Multiple Attachment Rails 4

只需按照以下步骤操作即可。

rails new multiple_image_upload_carrierwave

在 gem 文件中

gem 'carrierwave'
bundle install
rails generate uploader Avatar 

创建脚手架

rails generate scaffold post title:string

创建 post_attachment 脚手架

rails generate scaffold post_attachment post_id:integer avatar:string

rake db:migrate

在 post.rb 中

class Post < ActiveRecord::Base
   has_many :post_attachments
   accepts_nested_attributes_for :post_attachments
end

在 post_attachment.rb 中

class PostAttachment < ActiveRecord::Base
   mount_uploader :avatar, AvatarUploader
   belongs_to :post
end

在 post_controller.rb 中

def show
   @post_attachments = @post.post_attachments.all
end

def new
   @post = Post.new
   @post_attachment = @post.post_attachments.build
end

def create
   @post = Post.new(post_params)

   respond_to do |format|
     if @post.save
       params[:post_attachments]['avatar'].each do |a|
          @post_attachment = @post.post_attachments.create!(:avatar => a)
       end
       format.html  redirect_to @post, notice: 'Post was successfully created.' 
     else
       format.html  render action: 'new' 
     end
   end
 end

 private
   def post_params
      params.require(:post).permit(:title, post_attachments_attributes: [:id, :post_id, :avatar])
   end

在views/posts/_form.html.erb中

<%= form_for(@post, :html =>  :multipart => true ) do |f| %>
   <div class="field">
     <%= f.label :title %><br>
     <%= f.text_field :title %>
   </div>

   <%= f.fields_for :post_attachments do |p| %>
     <div class="field">
       <%= p.label :avatar %><br>
       <%= p.file_field :avatar, :multiple => true, name: "post_attachments[avatar][]" %>
     </div>
   <% end %>

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

编辑任何帖子的附件和附件列表。 在views/posts/show.html.erb中

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

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<% @post_attachments.each do |p| %>
  <%= image_tag p.avatar_url %>
  <%= link_to "Edit Attachment", edit_post_attachment_path(p) %>
<% end %>

<%= link_to 'Edit', edit_post_path(@post) %> |
<%= link_to 'Back', posts_path %>

更新表单以编辑附件views/post_attachments/_form.html.erb

<%= image_tag @post_attachment.avatar %>
<%= form_for(@post_attachment) do |f| %>
  <div class="field">
    <%= f.label :avatar %><br>
    <%= f.file_field :avatar %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

修改post_attachment_controller.rb中的更新方法

def update
  respond_to do |format|
    if @post_attachment.update(post_attachment_params)
      format.html  redirect_to @post_attachment.post, notice: 'Post attachment was successfully updated.' 
    end 
  end
end

在 rails 3 中无需定义强参数,您可以在模型中定义 attribute_accessible 并在模型中定义 accept_nested_attribute 来发布模型,因为在 rails 4 中不推荐使用属性可访问性。

对于编辑附件,我们不能一次修改所有附件。所以我们会一一替换附件,或者你可以根据你的规则修改,这里我只是告诉你如何更新任何附件。

【讨论】:

在后控制器的显示动作中,我想你忘记了@post =Post.find(params[:id]) @s-s-r 为什么在create 操作中循环浏览每个帖子附件? Rails 和carrierwave 足够智能,可以自动保存收藏。 希望看到编辑(尤其是处理:_destroy 部分) @s-s-r - 你的回答很有帮助。您能否也通过编辑操作更新您的答案。 当我向 post_attachment 模型添加验证时,它们不会阻止 post 模型保存。而是保存帖子,然后仅为附件模型引发 ActiveRecord 无效错误。我想这是因为创造!方法。但是使用 create 只是默默地失败。知道如何在帖子到达附件时进行验证吗?【参考方案2】:

如果我们看一下 CarrierWave 的文档,现在这实际上非常简单。

https://github.com/carrierwaveuploader/carrierwave/blob/master/README.md#multiple-file-uploads

我将使用Product作为我要添加图片的模型,作为示例。

    获取主分支 Carrierwave 并将其添加到您的 Gemfile:

    gem 'carrierwave', github:'carrierwaveuploader/carrierwave'
    

    在预期模型中创建一个列来托管图像数组:

    rails generate migration AddPicturesToProducts pictures:json
    

    运行迁移

    bundle exec rake db:migrate
    

    为模型产品添加图片

    app/models/product.rb
    
    class Product < ActiveRecord::Base
      validates :name, presence: true
      mount_uploaders :pictures, PictureUploader
    end
    

    在 ProductsController 中为强参数添加图片

    app/controllers/products_controller.rb
    
    def product_params
      params.require(:product).permit(:name, pictures: [])
    end
    

    允许您的表单接受多张图片

    app/views/products/new.html.erb
    
    # notice 'html:  multipart: true '
    <%= form_for @product, html:  multipart: true  do |f| %>
      <%= f.label :name %>
      <%= f.text_field :name %>
    
      # notice 'multiple: true'
      <%= f.label :pictures %>
      <%= f.file_field :pictures, multiple: true, accept: "image/jpeg, image/jpg, image/gif, image/png" %>
    
      <%= f.submit "Submit" %>
    <% end %>
    

    在您的视图中,您可以引用解析图片数组的图像:

    @product.pictures[1].url
    

如果您从一个文件夹中选择多个图像,则顺序将与您从上到下的确切顺序相同。

【讨论】:

CarrierWave 对这个问题的解决方案让我感到畏缩。它涉及将所有对文件的引用放入数组中的一个字段中!它当然不会被认为是“铁路方式”。如果您想删除一些文件或在帖子中添加额外文件怎么办?我不是说这不可能,我只是说这会很丑。连接表是一个更好的主意。 托比我完全同意。您能提供这样的解决方案吗? s-s-r 已经提供了该解决方案。放置另一个模型来保存上传的文件,然后需要上传许多文件的东西与另一个模型建立一对多或多对多的关系。 (我在之前的评论中提到的连接表将是多对多关系的情况) 谢谢@Toby1Kenobi,我想知道列数组方法如何解释图像版本(我不明白它是怎么做到的)。你的策略是可行的。 我已经用 Rails 5.xx 实现了 Carrierwave 的这个功能,github.com/carrierwaveuploader/carrierwave/blob/master/… 但是我无法成功运行它,并且它正在产生错误,UndefinedConversionError ("\x89" from ASCII-8BIT to UTF-8) 对于 s-s-r 解决方案,它工作正常使用 Rails 4.xx,但我面临挑战(使用 Rails 5.xx),即它在数据库中存储 ActionDispatch::Http::UploadedFile 而不是文件名。它也不会将文件存储在上传器中给定路径的公共文件夹中。【参考方案3】:

s-s-r 答案的一些小补充:

accepts_nested_attributes_for 不需要您更改父对象的控制器。所以如果要纠正

name: "post_attachments[avatar][]"

name: "post[post_attachments_attributes][][avatar]"

那么像这样的所有这些控制器更改都变得多余:

params[:post_attachments]['avatar'].each do |a|
  @post_attachment = @post.post_attachments.create!(:avatar => a)
end

您还应该在父对象表单中添加PostAttachment.new

在views/posts/_form.html.erb中

  <%= f.fields_for :post_attachments, PostAttachment.new do |ff| %>
    <div class="field">
      <%= ff.label :avatar %><br>
      <%= ff.file_field :avatar, :multiple => true, name: "post[post_attachments_attributes][][avatar]" %>
    </div>
  <% end %>

这会使父控制器中的这种更改变得多余:

@post_attachment = @post.post_attachments.build

欲了解更多信息,请参阅Rails fields_for form not showing up, nested form

如果你使用 Rails 5,那么由于 accepts_nested_attributes_for 中的错误(否则 accepts_nested_attributes_for 一般不能在 Rails 5 下工作)。

编辑 1:

添加关于destroy

在models/post.rb中

class Post < ApplicationRecord
    ...
    accepts_nested_attributes_for :post_attachments, allow_destroy: true
end

在views/posts/_form.html.erb中

 <% f.object.post_attachments.each do |post_attachment| %>
    <% if post_attachment.id %>

      <%

      post_attachments_delete_params =
      
      post:
                      
          post_attachments_attributes:  id: post_attachment.id, _destroy: true 
        
      

      %>

      <%= link_to "Delete", post_path(f.object.id, post_attachments_delete_params), method: :patch, data:  confirm: 'Are you sure?'  %>

      <br><br>
    <% end %>
  <% end %>

这样,您根本就不需要拥有子对象的控制器!我的意思是不再需要任何PostAttachmentsController。至于父对象的控制器 (PostController),您也几乎不会更改它 - 您唯一更改的是白名单参数列表(包括与子对象相关的参数) 像这样:

def post_params
  params.require(:post).permit(:title, :text, 
    post_attachments_attributes: ["avatar", "@original_filename", "@content_type", "@headers", "_destroy", "id"])
end

这就是accepts_nested_attributes_for 如此神奇的原因。

【讨论】:

这些实际上是对@s-s-r 答案的主要补充,而不是次要:) accept_nested_attributes_for 相当重要。事实上,根本不需要子控制器。按照您的方法,我唯一无法做的就是在上传出现问题时为孩子显示表单错误消息。 感谢您的意见。我得到了上传工作,但我想知道如何在 view/posts/_form.html.erb 中的 post_attachments 表单字段中添加其他属性? &lt;%= d.text_field :copyright, name: "album[diapos_attributes][][copyright]", class: 'form-field' %&gt; 仅将版权写入最后一条记录,而不是所有记录。【参考方案4】:

我还想出了如何更新多文件上传,我还对其进行了一些重构。这段代码是我的,但你明白了。

def create
  @motherboard = Motherboard.new(motherboard_params)
  if @motherboard.save
    save_attachments if params[:motherboard_attachments]
    redirect_to @motherboard, notice: 'Motherboard was successfully created.'
  else
    render :new
  end
end


def update
  update_attachments if params[:motherboard_attachments]
  if @motherboard.update(motherboard_params)
    redirect_to @motherboard, notice: 'Motherboard was successfully updated.'
  else
   render :edit
  end
end

private
def save_attachments
  params[:motherboard_attachments]['photo'].each do |photo|
    @motherboard_attachment = @motherboard.motherboard_attachments.create!(:photo => photo)
  end
end

 def update_attachments
   @motherboard.motherboard_attachments.each(&:destroy) if @motherboard.motherboard_attachments.present?
   params[:motherboard_attachments]['photo'].each do |photo|
     @motherboard_attachment = @motherboard.motherboard_attachments.create!(:photo => photo)
   end
 end

【讨论】:

感谢您分享您的代码。有时间请在我的 gihub repo 更新代码,不要忘记对每个方法进行评论,以便每个人都可以轻松理解代码。 我克隆了repos,你能允许我做PR吗? 是的,当然。你的github用户名是什么 你有机会给我访问权限吗?【参考方案5】:

这是我对模型的第二次重构:

    将私有方法移至模型。 将@motherboard 替换为self。

控制器:

def create
  @motherboard = Motherboard.new(motherboard_params)

  if @motherboard.save
    @motherboard.save_attachments(params) if params[:motherboard_attachments]
  redirect_to @motherboard, notice: 'Motherboard was successfully created.'
  else
    render :new
  end
end

def update
  @motherboard.update_attachments(params) if params[:motherboard_attachments]
  if @motherboard.update(motherboard_params)
    redirect_to @motherboard, notice: 'Motherboard was successfully updated.'
  else
    render :edit
  end
end

在主板型号中:

def save_attachments(params)
  params[:motherboard_attachments]['photo'].each do |photo|
    self.motherboard_attachments.create!(:photo => photo)
  end
end

def update_attachments(params)
  self.motherboard_attachments.each(&:destroy) if self.motherboard_attachments.present?
  params[:motherboard_attachments]['photo'].each do |photo|
    self.motherboard_attachments.create!(:photo => photo)
  end
end

【讨论】:

【参考方案6】:

使用@post.post_attachments关联时,不需要设置post_id

【讨论】:

以上是关于Rails 4使用carrierwave上传多个图像或文件的主要内容,如果未能解决你的问题,请参考以下文章

Rails 4,Carrierwave,jcrop,移动到 POST,而不是 UPDATE

rails4.2.8carrierwave+mini_magick+Jcrop,图片上传失败

在rails和carrierwave中上传之前显示图像预览的最佳方式

Rails Rollback Transaction 尝试使用carrierwave上传图片时

根据另一个模型的属性 - RAILS,为一个模型使用不同的CarrierWave上传器

Rails 4: Carrierwave/AWS - Excon::Errors::SocketError / getaddrinfo: nodename or servname provided